/ 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>