/ ui_components.html
ui_components.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> 6 7 <title>UI Components — Space.js</title> 8 9 <link rel="preconnect" href="https://fonts.gstatic.com"> 10 <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300&family=Gothic+A1:wght@500;700"> 11 <link rel="stylesheet" href="assets/css/style.css"> 12 13 <style> 14 *, :after, :before { 15 touch-action: unset; 16 } 17 18 body { 19 position: unset; 20 } 21 22 .ui > .info { 23 background-color: var(--bg-color); 24 border-radius: 4px; 25 } 26 </style> 27 28 <script type="module"> 29 import { DetailsButton, Interface, LineCanvas, Link, MuteButton, NavLink, Point, Reticle, ReticleCanvas, TargetNumber, Tracker, UI, Vector2, defer, getConstructor, ticker } from './src/index.js'; 30 31 class ComponentItem extends Interface { 32 constructor(view, index) { 33 super('.component-item'); 34 35 this.view = view; 36 this.index = index; 37 38 const size = 92; 39 40 this.width = size; 41 this.height = size; 42 this.animatedIn = false; 43 44 this.init(); 45 this.initViews(); 46 47 this.addListeners(); 48 } 49 50 init() { 51 this.css({ 52 position: 'relative', 53 boxSizing: 'border-box', 54 width: this.width, 55 height: this.height, 56 marginTop: 20, 57 display: 'flex', 58 justifyContent: 'center', 59 alignItems: 'center', 60 border: '1px solid var(--ui-color-divider-line)', 61 cursor: 'pointer', 62 opacity: 0 63 }); 64 65 this.number = new Interface('.number'); 66 this.number.css({ 67 position: 'absolute', 68 left: 0, 69 top: -20, 70 whiteSpace: 'nowrap' 71 }); 72 this.number.text(this.index + 1); 73 this.add(this.number); 74 75 this.type = new Interface('.type'); 76 this.type.css({ 77 position: 'relative', 78 top: -1, 79 display: 'inline-block', 80 marginLeft: 10, 81 fontSize: 'var(--ui-secondary-font-size)', 82 letterSpacing: 'var(--ui-secondary-letter-spacing)', 83 color: 'var(--ui-secondary-color)' 84 }); 85 this.type.text(getConstructor(this.view).name); 86 this.number.add(this.type); 87 } 88 89 initViews() { 90 if (this.view instanceof Interface) { 91 this.view.css({ pointerEvents: 'none' }); 92 } 93 94 this.add(this.view); 95 } 96 97 addListeners() { 98 if (this.view.onHover) { 99 this.element.addEventListener('mouseenter', this.onHover); 100 this.element.addEventListener('mouseleave', this.onHover); 101 } 102 103 this.element.addEventListener('click', this.onClick); 104 } 105 106 // Event handlers 107 108 onHover = e => { 109 this.view.onHover(e); 110 }; 111 112 onClick = e => { 113 e.stopPropagation(); 114 115 if (this.animatedIn) { 116 this.view.animateOut(); 117 this.animatedIn = false; 118 } else { 119 this.view.animateIn(); 120 this.animatedIn = true; 121 } 122 }; 123 124 // Public methods 125 126 animateIn = delay => { 127 this.tween({ opacity: 1 }, 400, 'easeOutCubic', delay); 128 129 this.view.animateIn(); 130 this.animatedIn = true; 131 }; 132 } 133 134 class Components extends Interface { 135 constructor() { 136 super('.components'); 137 138 this.itemWidth = 90; 139 this.itemHeight = 90; 140 this.itemHalfWidth = Math.round(this.itemWidth / 2); 141 this.itemHalfHeight = Math.round(this.itemHeight / 2); 142 this.items = []; 143 144 this.init(); 145 this.initCanvas(); 146 this.initViews(); 147 148 this.addListeners(); 149 this.onResize(); 150 } 151 152 init() { 153 this.css({ 154 minHeight: '100%', 155 display: 'flex', 156 justifyContent: 'center', 157 alignItems: 'center', 158 padding: '55px 0 125px' 159 }); 160 161 this.container = new Interface('.container'); 162 this.container.css({ 163 position: 'relative', 164 display: 'flex', 165 justifyContent: 'center', 166 alignItems: 'center', 167 flexWrap: 'wrap', 168 gap: 67, 169 maxWidth: 1205 170 }); 171 this.add(this.container); 172 } 173 174 initCanvas() { 175 this.canvas = new Interface(null, 'canvas'); 176 this.canvas.css({ 177 position: 'absolute', 178 left: 0, 179 top: 0, 180 pointerEvents: 'none' 181 }); 182 this.context = this.canvas.element.getContext('2d'); 183 this.container.add(this.canvas); 184 } 185 186 initViews() { 187 const width = this.itemWidth; 188 const height = this.itemHeight; 189 const halfWidth = this.itemHalfWidth; 190 const halfHeight = this.itemHalfHeight; 191 192 // Reticle 193 const reticle = new Reticle(); 194 reticle.position.set(halfWidth, halfHeight); 195 reticle.update(); 196 197 // Reticle with info 198 const reticle2 = new Reticle(); 199 reticle2.setData({ 200 primary: '127.0.0.1', 201 secondary: 'localhost' 202 }); 203 reticle2.position.set(halfWidth, halfHeight); 204 reticle2.update(); 205 206 // Reticle canvas 207 const reticle3 = new ReticleCanvas(); 208 reticle3.setContext(this.context); 209 210 // Tracker 211 const tracker = new Tracker(); 212 tracker.position.set(halfWidth, halfHeight); 213 tracker.update(); 214 tracker.css({ 215 width, 216 height, 217 marginLeft: -halfWidth, 218 marginTop: -halfHeight 219 }); 220 221 // Tracker with target number 222 const tracker2 = new Tracker(); 223 tracker2.setData({ targetNumber: 1 }); 224 tracker2.position.set(halfWidth, halfHeight); 225 tracker2.update(); 226 tracker2.css({ 227 width, 228 height, 229 marginLeft: -halfWidth, 230 marginTop: -halfHeight 231 }); 232 tracker2.lock(); 233 234 // Tracker with target number and reticle info 235 const tracker3 = new Tracker({ 236 noCorners: true 237 }); 238 tracker3.setData({ 239 targetNumber: 1, 240 primary: '127.0.0.1', 241 secondary: 'localhost' 242 }); 243 tracker3.position.set(halfWidth, halfHeight); 244 tracker3.update(); 245 tracker3.css({ 246 width, 247 height, 248 marginLeft: -halfWidth, 249 marginTop: -halfHeight 250 }); 251 tracker3.lock(); 252 253 // Target number 254 const number = new TargetNumber(); 255 number.setData({ targetNumber: 1 }); 256 257 // Line canvas 258 const line = new LineCanvas(); 259 line.setContext(this.context); 260 261 // Point 262 const point = new Point(); 263 point.setData({ 264 name: '127.0.0.1', 265 type: 'localhost' 266 }); 267 268 // Point with multiple target numbers 269 const point2 = new Point(); 270 point2.setData({ 271 name: '127.0.0.1', 272 type: 'localhost' 273 }); 274 point2.setTargetNumbers([1, 2, 3]); 275 point2.lock(); 276 277 // Details button (closed) 278 const detailsButton = new DetailsButton(); 279 detailsButton.css({ marginLeft: 20 }); 280 281 // Details button (open) 282 const detailsButton2 = new DetailsButton(); 283 detailsButton2.css({ marginLeft: 20 }); 284 detailsButton2.open(); 285 286 // Details button with number (closed) 287 const detailsButton3 = new DetailsButton(); 288 detailsButton3.css({ marginLeft: 20 }); 289 detailsButton3.setData({ number: 1 }); 290 291 // Details button with number (open) 292 const detailsButton4 = new DetailsButton(); 293 detailsButton4.css({ marginLeft: 20 }); 294 detailsButton4.setData({ number: 1 }); 295 detailsButton4.open(); 296 297 // Details button with number and total (closed) 298 const detailsButton5 = new DetailsButton(); 299 detailsButton5.css({ marginLeft: 20 }); 300 detailsButton5.setData({ 301 number: 1, 302 total: 6 303 }); 304 305 // Details button with number and total (open) 306 const detailsButton6 = new DetailsButton(); 307 detailsButton6.css({ marginLeft: 20 }); 308 detailsButton6.setData({ 309 number: 1, 310 total: 6 311 }); 312 detailsButton6.open(); 313 314 // Mute button (sound on) 315 const muteButton = new MuteButton({ sound: true }); 316 317 // Mute button (sound off) 318 const muteButton2 = new MuteButton({ sound: false }); 319 320 // Nav link 321 const link = new NavLink({ title: 'Link' }); 322 323 // Link 324 const link2 = new Link({ title: 'Link' }); 325 326 // Create components 327 [ 328 reticle, 329 reticle2, 330 reticle3, 331 tracker, 332 tracker2, 333 tracker3, 334 number, 335 line, 336 point, 337 point2, 338 detailsButton, 339 detailsButton2, 340 detailsButton3, 341 detailsButton4, 342 detailsButton5, 343 detailsButton6, 344 muteButton, 345 muteButton2, 346 link, 347 link2 348 ].forEach((view, i) => { 349 const item = new ComponentItem(view, i); 350 this.container.add(item); 351 this.items.push(item); 352 }); 353 354 // Public properties 355 this.reticle3 = reticle3; 356 this.line = line; 357 this.detailsButton = detailsButton; 358 this.detailsButton2 = detailsButton2; 359 this.detailsButton3 = detailsButton3; 360 this.detailsButton4 = detailsButton4; 361 this.detailsButton5 = detailsButton5; 362 this.detailsButton6 = detailsButton6; 363 this.muteButton = muteButton; 364 this.muteButton2 = muteButton2; 365 } 366 367 addListeners() { 368 document.body.addEventListener('click', this.onClick); 369 window.addEventListener('resize', this.onResize); 370 ticker.add(this.onUpdate); 371 } 372 373 // Event handlers 374 375 onClick = () => { 376 this.detailsButton3.setData({ 377 number: Number(this.detailsButton3.number.text()) + 1 378 }); 379 380 this.detailsButton4.setData({ 381 number: Number(this.detailsButton4.number.text()) + 1 382 }); 383 384 this.detailsButton5.setData({ 385 number: Number(this.detailsButton6.number.text()) + 1 386 }); 387 388 this.detailsButton6.setData({ 389 number: Number(this.detailsButton6.number.text()) + 1 390 }); 391 }; 392 393 onResize = async () => { 394 await defer(); 395 396 const containerBounds = this.container.element.getBoundingClientRect(); 397 const width = containerBounds.width; 398 const height = containerBounds.height; 399 const dpr = window.devicePixelRatio; 400 401 this.canvas.element.width = Math.round(width * dpr); 402 this.canvas.element.height = Math.round(height * dpr); 403 this.canvas.element.style.width = `${width}px`; 404 this.canvas.element.style.height = `${height}px`; 405 this.context.scale(dpr, dpr); 406 407 // Reticle 408 const reticleBounds = this.items[2].element.getBoundingClientRect(); 409 const reticleLeft = reticleBounds.left - containerBounds.left; 410 const reticleTop = reticleBounds.top - containerBounds.top; 411 412 this.reticle3.position.set(reticleLeft + this.itemHalfWidth, reticleTop + this.itemHalfHeight); 413 414 // Line 415 const lineBounds = this.items[7].element.getBoundingClientRect(); 416 const lineLeft = lineBounds.left - containerBounds.left; 417 const lineTop = lineBounds.top - containerBounds.top; 418 419 this.line.setStartPoint(new Vector2(lineLeft + 1, lineTop + this.itemHeight + 1)); 420 this.line.setEndPoint(new Vector2(lineLeft + this.itemWidth + 1, lineTop + 1)); 421 422 // Buttons 423 this.detailsButton.resize(); 424 this.detailsButton2.resize(); 425 this.detailsButton3.resize(); 426 this.detailsButton4.resize(); 427 this.detailsButton5.resize(); 428 this.detailsButton6.resize(); 429 this.muteButton.resize(); 430 this.muteButton2.resize(); 431 432 this.onUpdate(); 433 }; 434 435 onUpdate = () => { 436 this.context.clearRect(0, 0, this.canvas.element.width, this.canvas.element.height); 437 438 this.reticle3.update(); 439 this.line.update(); 440 this.detailsButton.update(); 441 this.detailsButton2.update(); 442 this.detailsButton3.update(); 443 this.detailsButton4.update(); 444 this.detailsButton5.update(); 445 this.detailsButton6.update(); 446 this.muteButton.update(); 447 this.muteButton2.update(); 448 }; 449 450 // Public methods 451 452 animateIn = () => { 453 this.items.forEach((item, i) => item.animateIn(i * 50)); 454 }; 455 } 456 457 class App { 458 static async init() { 459 this.initViews(); 460 461 this.addListeners(); 462 } 463 464 static initViews() { 465 this.ui = new UI({ 466 instructions: { 467 content: `${navigator.maxTouchPoints ? 'Tap' : 'Click'} each component to toggle` 468 } 469 }); 470 this.ui.instructions.animateIn(); 471 document.body.appendChild(this.ui.element); 472 473 this.components = new Components(); 474 document.body.appendChild(this.components.element); 475 } 476 477 static addListeners() { 478 window.addEventListener('load', this.onLoad); 479 ticker.start(); 480 } 481 482 // Event handlers 483 484 static onLoad = () => { 485 this.components.animateIn(); 486 this.ui.animateIn(); 487 }; 488 } 489 490 App.init(); 491 </script> 492 </head> 493 <body> 494 </body> 495 </html>