1import {css, html, LitElement} from 'lit'; 2import {customElement, property} from 'lit/decorators.js'; 3import {live} from 'lit/directives/live.js'; 4import {styleMap} from 'lit/directives/style-map.js'; 5 6import {Device, Notifiable, SimulationInfo, simulationState,} from './device-observer.js'; 7import {State} from './model.js' 8 9 @customElement('ns-device-info') export class DeviceInformation extends LitElement implements Notifiable { 10 11 // Selected Device on scene 12 @property() selectedDevice: Device|undefined; 13 14 /** 15 * the yaw value in orientation for ns-cube-sprite 16 * unit: deg 17 */ 18 @property({type : Number}) yaw = 0; 19 20 /** 21 * the pitch value in orientation for ns-cube-spriteß 22 * unit: deg 23 */ 24 @property({type : Number}) pitch = 0; 25 26 /** 27 * the roll value in orientation for ns-cube-sprite 28 * unit: deg 29 */ 30 @property({type : Number}) roll = 0; 31 32 /** 33 * The state of device info. True if edit mode. 34 */ 35 @property({type : Boolean}) editMode = false; 36 37 /** 38 * the x value in position for ns-cube-sprite 39 * unit: cm 40 */ 41 @property({type : Number}) posX = 0; 42 43 /** 44 * the y value in position for ns-cube-sprite 45 * unit: cm 46 */ 47 @property({type : Number}) posY = 0; 48 49 /** 50 * the z value in position for ns-cube-sprite 51 * unit: cm 52 */ 53 @property({type : Number}) posZ = 0; 54 55 holdRange = false; 56 57 static styles = css` 58 :host { 59 cursor: pointer; 60 display: grid; 61 place-content: center; 62 color: white; 63 font-size: 25px; 64 font-family: 'Lato', sans-serif; 65 border: 5px solid black; 66 border-radius: 12px; 67 padding: 10px; 68 background-color: #9199a5; 69 max-width: 600px; 70 } 71 72 .title { 73 font-weight: bold; 74 text-transform: uppercase; 75 text-align: center; 76 margin-bottom: 10px; 77 } 78 79 .setting { 80 display: grid; 81 grid-template-columns: auto auto; 82 margin-top: 0px; 83 margin-bottom: 30px; 84 //border: 3px solid black; 85 padding: 10px; 86 } 87 88 .setting .name { 89 grid-column: 1 / span 2; 90 text-transform: uppercase; 91 text-align: left; 92 margin-bottom: 10px; 93 font-weight: bold; 94 } 95 96 .label { 97 grid-column: 1; 98 text-align: left; 99 } 100 101 .info { 102 grid-column: 2; 103 text-align: right; 104 margin-bottom: 10px; 105 } 106 107 .switch { 108 position: relative; 109 float: right; 110 width: 60px; 111 height: 34px; 112 } 113 114 .switch input { 115 opacity: 0; 116 width: 0; 117 height: 0; 118 } 119 120 .slider { 121 position: absolute; 122 cursor: pointer; 123 top: 0; 124 left: 0; 125 right: 0; 126 bottom: 0; 127 background-color: #ccc; 128 -webkit-transition: 0.4s; 129 transition: 0.4s; 130 } 131 132 .slider:before { 133 position: absolute; 134 content: ''; 135 height: 26px; 136 width: 26px; 137 left: 4px; 138 bottom: 4px; 139 background-color: white; 140 -webkit-transition: 0.4s; 141 transition: 0.4s; 142 } 143 144 input:checked + .slider { 145 background-color: #2196f3; 146 } 147 148 input:focus + .slider { 149 box-shadow: 0 0 1px #2196f3; 150 } 151 152 input:checked + .slider:before { 153 -webkit-transform: translateX(26px); 154 -ms-transform: translateX(26px); 155 transform: translateX(26px); 156 } 157 158 /* Rounded sliders */ 159 .slider.round { 160 border-radius: 34px; 161 } 162 163 .slider.round:before { 164 border-radius: 50%; 165 } 166 167 .text { 168 display: inline-block; 169 position: relative; 170 width: 50px; 171 } 172 173 input[type='range'] { 174 width: 400px; 175 } 176 177 input[type='text'] { 178 width: 50%; 179 font-size: inherit; 180 text-align: right; 181 max-height: 25px; 182 } 183 184 input[type='text'].orientation { 185 max-width: 50px; 186 } 187 188 input[type='button'] { 189 display: inline; 190 font-size: inherit; 191 max-width: 200px; 192 } 193 `; 194 195 connectedCallback() { 196 super.connectedCallback(); // eslint-disable-line 197 simulationState.registerObserver(this); 198 } 199 200 disconnectedCallback() { 201 simulationState.removeObserver(this); 202 super.disconnectedCallback(); // eslint-disable-line 203 } 204 205 onNotify(data: SimulationInfo) { 206 if (data.selectedId && this.editMode === false) { 207 for (const device of data.devices) { 208 if (device.name === data.selectedId) { 209 this.selectedDevice = device; 210 if (!this.holdRange){ 211 this.yaw = device.orientation.yaw; 212 this.pitch = device.orientation.pitch; 213 this.roll = device.orientation.roll; 214 } 215 this.posX = Math.floor(device.position.x * 100); 216 this.posY = Math.floor(device.position.y * 100); 217 this.posZ = Math.floor(device.position.z * 100); 218 break; 219 } 220 } 221 } 222 } 223 224 private changeRange(ev: InputEvent) { 225 this.holdRange = true; 226 console.assert(this.selectedDevice !== null); // eslint-disable-line 227 const range = ev.target as HTMLInputElement; 228 const event = new CustomEvent('orientationEvent', { 229 detail: { 230 name: this.selectedDevice?.name, 231 type: range.id, 232 value: range.value, 233 }, 234 }); 235 window.dispatchEvent(event); 236 if (range.id === 'yaw') { 237 this.yaw = Number(range.value); 238 } else if (range.id === 'pitch') { 239 this.pitch = Number(range.value); 240 } else { 241 this.roll = Number(range.value); 242 } 243 } 244 245 private patchOrientation() { 246 this.holdRange = false; 247 console.assert(this.selectedDevice !== undefined); // eslint-disable-line 248 if (this.selectedDevice === undefined) return; 249 this.selectedDevice.orientation = {yaw: this.yaw, pitch: this.pitch, roll: this.roll}; 250 simulationState.patchDevice({ 251 device: { 252 name: this.selectedDevice.name, 253 orientation: this.selectedDevice.orientation, 254 }, 255 }); 256 } 257 258 private patchRadio() { 259 console.assert(this.selectedDevice !== undefined); // eslint-disable-line 260 if (this.selectedDevice === undefined) return; 261 simulationState.patchDevice({ 262 device: { 263 name: this.selectedDevice.name, 264 chips: this.selectedDevice.chips, 265 }, 266 }); 267 } 268 269 private handleEditForm() { 270 if (this.editMode) { 271 simulationState.invokeGetDevice(); 272 this.editMode = false; 273 } else { 274 this.editMode = true; 275 } 276 } 277 278 static checkPositionBound(value: number) { 279 return value > 10 ? 10 : value < 0 ? 0 : value; // eslint-disable-line 280 } 281 282 static checkOrientationBound(value: number) { 283 return value > 90 ? 90 : value < -90 ? -90 : value; // eslint-disable-line 284 } 285 286 private handleSave() { 287 console.assert(this.selectedDevice !== undefined); // eslint-disable-line 288 if (this.selectedDevice === undefined) return; 289 const elements = this.renderRoot.querySelectorAll(`[id^="edit"]`); 290 const obj: Record<string, any> = { 291 name: this.selectedDevice.name, 292 position: this.selectedDevice.position, 293 orientation: this.selectedDevice.orientation, 294 }; 295 elements.forEach(element => { 296 const inputElement = element as HTMLInputElement; 297 if (inputElement.id === 'editName') { 298 obj.name = inputElement.value; 299 } else if (inputElement.id.startsWith('editPos')) { 300 if (!Number.isNaN(Number(inputElement.value))) { 301 obj.position[inputElement.id.slice(7).toLowerCase()] = 302 DeviceInformation.checkPositionBound( 303 Number(inputElement.value) / 100 304 ); 305 } 306 } else if (inputElement.id.startsWith('editOri')) { 307 if (!Number.isNaN(Number(inputElement.value))) { 308 obj.orientation[inputElement.id.slice(7).toLowerCase()] = 309 DeviceInformation.checkOrientationBound(Number(inputElement.value)); 310 } 311 } 312 }); 313 this.selectedDevice.name = obj.name; 314 this.selectedDevice.position = obj.position; 315 this.selectedDevice.orientation = obj.orientation; 316 this.handleEditForm(); 317 simulationState.patchDevice({ 318 device: obj, 319 }); 320 } 321 322 private handleGetChips() { 323 const disabledCheckbox = html` 324 <input type="checkbox" disabled /> 325 <span 326 class="slider round" 327 style=${styleMap({ opacity: '0.7' })} 328 ></span> 329 `; 330 let lowEnergyCheckbox = disabledCheckbox; 331 let classicCheckbox = disabledCheckbox; 332 let wifiCheckbox = disabledCheckbox; 333 let uwbCheckbox = disabledCheckbox; 334 if (this.selectedDevice) { 335 if ('chips' in this.selectedDevice && this.selectedDevice.chips) { 336 for (const chip of this.selectedDevice.chips) { 337 if ('bt' in chip && chip.bt) { 338 if ('lowEnergy' in chip.bt && chip.bt.lowEnergy && 'state' in chip.bt.lowEnergy) { 339 lowEnergyCheckbox = html ` 340 <input 341 id="lowEnergy" 342 type="checkbox" 343 .checked=${live(chip.bt.lowEnergy.state === State.ON)} 344 @click=${() => { 345 // eslint-disable-next-line 346 this.selectedDevice?.toggleChipState(chip, 'lowEnergy'); 347 this.patchRadio(); 348 }} 349 /> 350 <span class="slider round"></span> 351 `; 352 } 353 if ('classic' in chip.bt && chip.bt.classic && 'state' in chip.bt.classic) { 354 classicCheckbox = html` 355 <input 356 id="classic" 357 type="checkbox" 358 .checked=${live(chip.bt.classic.state === State.ON)} 359 @click=${() => { 360 // eslint-disable-next-line 361 this.selectedDevice?.toggleChipState(chip, 'classic'); 362 this.patchRadio(); 363 }} 364 /> 365 <span class="slider round"></span> 366 `; 367 } 368 } 369 370 if ('wifi' in chip && chip.wifi) { 371 wifiCheckbox = html` 372 <input 373 id="wifi" 374 type="checkbox" 375 .checked=${live(chip.wifi.state === State.ON)} 376 @click=${() => { 377 // eslint-disable-next-line 378 this.selectedDevice?.toggleChipState(chip); 379 this.patchRadio(); 380 }} 381 /> 382 <span class="slider round"></span> 383 `; 384 } 385 386 if ('uwb' in chip && chip.uwb) { 387 uwbCheckbox = html` 388 <input 389 id="uwb" 390 type="checkbox" 391 .checked=${live(chip.uwb.state === State.ON)} 392 @click=${() => { 393 // eslint-disable-next-line 394 this.selectedDevice?.toggleChipState(chip); 395 this.patchRadio(); 396 }} 397 /> 398 <span class="slider round"></span> 399 `; 400 } 401 } 402 } 403 } 404 return html` 405 <div class="label">BLE</div> 406 <div class="info"> 407 <label class="switch"> 408 ${lowEnergyCheckbox} 409 </label> 410 </div> 411 <div class="label">Classic</div> 412 <div class="info"> 413 <label class="switch"> 414 ${classicCheckbox} 415 </label> 416 </div> 417 <div class="label">WIFI</div> 418 <div class="info"> 419 <label class="switch"> 420 ${wifiCheckbox} 421 </label> 422 </div> 423 <div class="label">UWB</div> 424 <div class="info"> 425 <label class="switch"> 426 ${uwbCheckbox} 427 </label> 428 </div> 429 `; 430 } 431 432 render() { 433 return html`${this.selectedDevice 434 ? html` 435 <div class="title">Device Info</div> 436 <div class="setting"> 437 <div class="name">Name</div> 438 <div class="info">${this.selectedDevice.name}</div> 439 </div> 440 <div class="setting"> 441 <div class="name">Position</div> 442 <div class="label">X</div> 443 <div class="info" style=${styleMap({ color: 'red' })}> 444 ${this.editMode 445 ? html`<input 446 type="text" 447 id="editPosX" 448 .value=${this.posX.toString()} 449 />` 450 : html`${this.posX}`} 451 </div> 452 <div class="label">Y</div> 453 <div class="info" style=${styleMap({ color: 'green' })}> 454 ${this.editMode 455 ? html`<input 456 type="text" 457 id="editPosY" 458 .value=${this.posY.toString()} 459 />` 460 : html`${this.posY}`} 461 </div> 462 <div class="label">Z</div> 463 <div class="info" style=${styleMap({ color: 'blue' })}> 464 ${this.editMode 465 ? html`<input 466 type="text" 467 id="editPosZ" 468 .value=${this.posZ.toString()} 469 />` 470 : html`${this.posZ}`} 471 </div> 472 </div> 473 <div class="setting"> 474 <div class="name">Orientation</div> 475 <div class="label">Yaw</div> 476 <div class="info"> 477 <input 478 id="yaw" 479 type="range" 480 min="-90" 481 max="90" 482 .value=${this.yaw.toString()} 483 .disabled=${this.editMode} 484 @input=${this.changeRange} 485 @change=${this.patchOrientation} 486 /> 487 ${this.editMode 488 ? html`<input 489 type="text" 490 id="editOriYaw" 491 class="orientation" 492 .value=${this.yaw.toString()} 493 />` 494 : html`<div class="text">(${this.yaw})</div>`} 495 </div> 496 <div class="label">Pitch</div> 497 <div class="info"> 498 <input 499 id="pitch" 500 type="range" 501 min="-90" 502 max="90" 503 .value=${this.pitch.toString()} 504 .disabled=${this.editMode} 505 @input=${this.changeRange} 506 @change=${this.patchOrientation} 507 /> 508 ${this.editMode 509 ? html`<input 510 type="text" 511 id="editOriPitch" 512 class="orientation" 513 .value=${this.pitch.toString()} 514 />` 515 : html`<div class="text">(${this.pitch})</div>`} 516 </div> 517 <div class="label">Roll</div> 518 <div class="info"> 519 <input 520 id="roll" 521 type="range" 522 min="-90" 523 max="90" 524 .value=${this.roll.toString()} 525 .disabled=${this.editMode} 526 @input=${this.changeRange} 527 @change=${this.patchOrientation} 528 /> 529 ${this.editMode 530 ? html`<input 531 type="text" 532 id="editOriRoll" 533 class="orientation" 534 .value=${this.roll.toString()} 535 />` 536 : html`<div class="text">(${this.roll})</div>`} 537 </div> 538 </div> 539 <div class="setting"> 540 ${this.editMode 541 ? html` 542 <input type="button" value="Save" @click=${this.handleSave} /> 543 <input 544 type="button" 545 value="Cancel" 546 @click=${this.handleEditForm} 547 /> 548 ` 549 : html`<input 550 type="button" 551 value="Edit" 552 @click=${this.handleEditForm} 553 />`} 554 </div> 555 <div class="setting"> 556 <div class="name">Radio States</div> 557 ${this.handleGetChips()} 558 </div> 559 ` 560 : html`<div class="title">Device Info</div>`}`; 561 }} 562