github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/present/static/slides.js (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  var PERMANENT_URL_PREFIX = '/static/';
     6  
     7  var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next'];
     8  
     9  var PM_TOUCH_SENSITIVITY = 15;
    10  
    11  var curSlide;
    12  
    13  /* ---------------------------------------------------------------------- */
    14  /* classList polyfill by Eli Grey
    15   * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */
    16  
    17  if (
    18    typeof document !== 'undefined' &&
    19    !('classList' in document.createElement('a'))
    20  ) {
    21    (function(view) {
    22      var classListProp = 'classList',
    23        protoProp = 'prototype',
    24        elemCtrProto = (view.HTMLElement || view.Element)[protoProp],
    25        objCtr = Object;
    26      (strTrim =
    27        String[protoProp].trim ||
    28        function() {
    29          return this.replace(/^\s+|\s+$/g, '');
    30        }),
    31        (arrIndexOf =
    32          Array[protoProp].indexOf ||
    33          function(item) {
    34            for (var i = 0, len = this.length; i < len; i++) {
    35              if (i in this && this[i] === item) {
    36                return i;
    37              }
    38            }
    39            return -1;
    40          }),
    41        // Vendors: please allow content code to instantiate DOMExceptions
    42        (DOMEx = function(type, message) {
    43          this.name = type;
    44          this.code = DOMException[type];
    45          this.message = message;
    46        }),
    47        (checkTokenAndGetIndex = function(classList, token) {
    48          if (token === '') {
    49            throw new DOMEx(
    50              'SYNTAX_ERR',
    51              'An invalid or illegal string was specified'
    52            );
    53          }
    54          if (/\s/.test(token)) {
    55            throw new DOMEx(
    56              'INVALID_CHARACTER_ERR',
    57              'String contains an invalid character'
    58            );
    59          }
    60          return arrIndexOf.call(classList, token);
    61        }),
    62        (ClassList = function(elem) {
    63          var trimmedClasses = strTrim.call(elem.className),
    64            classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [];
    65          for (var i = 0, len = classes.length; i < len; i++) {
    66            this.push(classes[i]);
    67          }
    68          this._updateClassName = function() {
    69            elem.className = this.toString();
    70          };
    71        }),
    72        (classListProto = ClassList[protoProp] = []),
    73        (classListGetter = function() {
    74          return new ClassList(this);
    75        });
    76      // Most DOMException implementations don't allow calling DOMException's toString()
    77      // on non-DOMExceptions. Error's toString() is sufficient here.
    78      DOMEx[protoProp] = Error[protoProp];
    79      classListProto.item = function(i) {
    80        return this[i] || null;
    81      };
    82      classListProto.contains = function(token) {
    83        token += '';
    84        return checkTokenAndGetIndex(this, token) !== -1;
    85      };
    86      classListProto.add = function(token) {
    87        token += '';
    88        if (checkTokenAndGetIndex(this, token) === -1) {
    89          this.push(token);
    90          this._updateClassName();
    91        }
    92      };
    93      classListProto.remove = function(token) {
    94        token += '';
    95        var index = checkTokenAndGetIndex(this, token);
    96        if (index !== -1) {
    97          this.splice(index, 1);
    98          this._updateClassName();
    99        }
   100      };
   101      classListProto.toggle = function(token) {
   102        token += '';
   103        if (checkTokenAndGetIndex(this, token) === -1) {
   104          this.add(token);
   105        } else {
   106          this.remove(token);
   107        }
   108      };
   109      classListProto.toString = function() {
   110        return this.join(' ');
   111      };
   112  
   113      if (objCtr.defineProperty) {
   114        var classListPropDesc = {
   115          get: classListGetter,
   116          enumerable: true,
   117          configurable: true,
   118        };
   119        try {
   120          objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
   121        } catch (ex) {
   122          // IE 8 doesn't support enumerable:true
   123          if (ex.number === -0x7ff5ec54) {
   124            classListPropDesc.enumerable = false;
   125            objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
   126          }
   127        }
   128      } else if (objCtr[protoProp].__defineGetter__) {
   129        elemCtrProto.__defineGetter__(classListProp, classListGetter);
   130      }
   131    })(self);
   132  }
   133  /* ---------------------------------------------------------------------- */
   134  
   135  /* Slide movement */
   136  
   137  function hideHelpText() {
   138    document.getElementById('help').style.display = 'none';
   139  }
   140  
   141  function getSlideEl(no) {
   142    if (no < 0 || no >= slideEls.length) {
   143      return null;
   144    } else {
   145      return slideEls[no];
   146    }
   147  }
   148  
   149  function updateSlideClass(slideNo, className) {
   150    var el = getSlideEl(slideNo);
   151  
   152    if (!el) {
   153      return;
   154    }
   155  
   156    if (className) {
   157      el.classList.add(className);
   158    }
   159  
   160    for (var i in SLIDE_CLASSES) {
   161      if (className != SLIDE_CLASSES[i]) {
   162        el.classList.remove(SLIDE_CLASSES[i]);
   163      }
   164    }
   165  }
   166  
   167  function updateSlides() {
   168    if (window.trackPageview) window.trackPageview();
   169  
   170    for (var i = 0; i < slideEls.length; i++) {
   171      switch (i) {
   172        case curSlide - 2:
   173          updateSlideClass(i, 'far-past');
   174          break;
   175        case curSlide - 1:
   176          updateSlideClass(i, 'past');
   177          break;
   178        case curSlide:
   179          updateSlideClass(i, 'current');
   180          break;
   181        case curSlide + 1:
   182          updateSlideClass(i, 'next');
   183          break;
   184        case curSlide + 2:
   185          updateSlideClass(i, 'far-next');
   186          break;
   187        default:
   188          updateSlideClass(i);
   189          break;
   190      }
   191    }
   192  
   193    triggerLeaveEvent(curSlide - 1);
   194    triggerEnterEvent(curSlide);
   195  
   196    window.setTimeout(function() {
   197      // Hide after the slide
   198      disableSlideFrames(curSlide - 2);
   199    }, 301);
   200  
   201    enableSlideFrames(curSlide - 1);
   202    enableSlideFrames(curSlide + 2);
   203  
   204    updateHash();
   205  }
   206  
   207  function prevSlide() {
   208    hideHelpText();
   209    if (curSlide > 0) {
   210      curSlide--;
   211  
   212      updateSlides();
   213    }
   214  
   215    if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
   216  }
   217  
   218  function nextSlide() {
   219    hideHelpText();
   220    if (curSlide < slideEls.length - 1) {
   221      curSlide++;
   222  
   223      updateSlides();
   224    }
   225  
   226    if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
   227  }
   228  
   229  /* Slide events */
   230  
   231  function triggerEnterEvent(no) {
   232    var el = getSlideEl(no);
   233    if (!el) {
   234      return;
   235    }
   236  
   237    var onEnter = el.getAttribute('onslideenter');
   238    if (onEnter) {
   239      new Function(onEnter).call(el);
   240    }
   241  
   242    var evt = document.createEvent('Event');
   243    evt.initEvent('slideenter', true, true);
   244    evt.slideNumber = no + 1; // Make it readable
   245  
   246    el.dispatchEvent(evt);
   247  }
   248  
   249  function triggerLeaveEvent(no) {
   250    var el = getSlideEl(no);
   251    if (!el) {
   252      return;
   253    }
   254  
   255    var onLeave = el.getAttribute('onslideleave');
   256    if (onLeave) {
   257      new Function(onLeave).call(el);
   258    }
   259  
   260    var evt = document.createEvent('Event');
   261    evt.initEvent('slideleave', true, true);
   262    evt.slideNumber = no + 1; // Make it readable
   263  
   264    el.dispatchEvent(evt);
   265  }
   266  
   267  /* Touch events */
   268  
   269  function handleTouchStart(event) {
   270    if (event.touches.length == 1) {
   271      touchDX = 0;
   272      touchDY = 0;
   273  
   274      touchStartX = event.touches[0].pageX;
   275      touchStartY = event.touches[0].pageY;
   276  
   277      document.body.addEventListener('touchmove', handleTouchMove, true);
   278      document.body.addEventListener('touchend', handleTouchEnd, true);
   279    }
   280  }
   281  
   282  function handleTouchMove(event) {
   283    if (event.touches.length > 1) {
   284      cancelTouch();
   285    } else {
   286      touchDX = event.touches[0].pageX - touchStartX;
   287      touchDY = event.touches[0].pageY - touchStartY;
   288      event.preventDefault();
   289    }
   290  }
   291  
   292  function handleTouchEnd(event) {
   293    var dx = Math.abs(touchDX);
   294    var dy = Math.abs(touchDY);
   295  
   296    if (dx > PM_TOUCH_SENSITIVITY && dy < (dx * 2) / 3) {
   297      if (touchDX > 0) {
   298        prevSlide();
   299      } else {
   300        nextSlide();
   301      }
   302    }
   303  
   304    cancelTouch();
   305  }
   306  
   307  function cancelTouch() {
   308    document.body.removeEventListener('touchmove', handleTouchMove, true);
   309    document.body.removeEventListener('touchend', handleTouchEnd, true);
   310  }
   311  
   312  /* Preloading frames */
   313  
   314  function disableSlideFrames(no) {
   315    var el = getSlideEl(no);
   316    if (!el) {
   317      return;
   318    }
   319  
   320    var frames = el.getElementsByTagName('iframe');
   321    for (var i = 0, frame; (frame = frames[i]); i++) {
   322      disableFrame(frame);
   323    }
   324  }
   325  
   326  function enableSlideFrames(no) {
   327    var el = getSlideEl(no);
   328    if (!el) {
   329      return;
   330    }
   331  
   332    var frames = el.getElementsByTagName('iframe');
   333    for (var i = 0, frame; (frame = frames[i]); i++) {
   334      enableFrame(frame);
   335    }
   336  }
   337  
   338  function disableFrame(frame) {
   339    frame.src = 'about:blank';
   340  }
   341  
   342  function enableFrame(frame) {
   343    var src = frame._src;
   344  
   345    if (frame.src != src && src != 'about:blank') {
   346      frame.src = src;
   347    }
   348  }
   349  
   350  function setupFrames() {
   351    var frames = document.querySelectorAll('iframe');
   352    for (var i = 0, frame; (frame = frames[i]); i++) {
   353      frame._src = frame.src;
   354      disableFrame(frame);
   355    }
   356  
   357    enableSlideFrames(curSlide);
   358    enableSlideFrames(curSlide + 1);
   359    enableSlideFrames(curSlide + 2);
   360  }
   361  
   362  function setupInteraction() {
   363    /* Clicking and tapping */
   364  
   365    var el = document.createElement('div');
   366    el.className = 'slide-area';
   367    el.id = 'prev-slide-area';
   368    el.addEventListener('click', prevSlide, false);
   369    document.querySelector('section.slides').appendChild(el);
   370  
   371    var el = document.createElement('div');
   372    el.className = 'slide-area';
   373    el.id = 'next-slide-area';
   374    el.addEventListener('click', nextSlide, false);
   375    document.querySelector('section.slides').appendChild(el);
   376  
   377    /* Swiping */
   378  
   379    document.body.addEventListener('touchstart', handleTouchStart, false);
   380  }
   381  
   382  /* Hash functions */
   383  
   384  function getCurSlideFromHash() {
   385    var slideNo = parseInt(location.hash.substr(1));
   386  
   387    if (slideNo) {
   388      curSlide = slideNo - 1;
   389    } else {
   390      curSlide = 0;
   391    }
   392  }
   393  
   394  function updateHash() {
   395    location.replace('#' + (curSlide + 1));
   396  }
   397  
   398  /* Event listeners */
   399  
   400  function handleBodyKeyDown(event) {
   401    // If we're in a code element, only handle pgup/down.
   402    var inCode = event.target.classList.contains('code');
   403  
   404    switch (event.keyCode) {
   405      case 78: // 'N' opens presenter notes window
   406        if (!inCode && notesEnabled) toggleNotesWindow();
   407        break;
   408      case 72: // 'H' hides the help text
   409      case 27: // escape key
   410        if (!inCode) hideHelpText();
   411        break;
   412  
   413      case 39: // right arrow
   414      case 13: // Enter
   415      case 32: // space
   416        if (inCode) break;
   417      case 34: // PgDn
   418        nextSlide();
   419        event.preventDefault();
   420        break;
   421  
   422      case 37: // left arrow
   423      case 8: // Backspace
   424        if (inCode) break;
   425      case 33: // PgUp
   426        prevSlide();
   427        event.preventDefault();
   428        break;
   429  
   430      case 40: // down arrow
   431        if (inCode) break;
   432        nextSlide();
   433        event.preventDefault();
   434        break;
   435  
   436      case 38: // up arrow
   437        if (inCode) break;
   438        prevSlide();
   439        event.preventDefault();
   440        break;
   441    }
   442  }
   443  
   444  function scaleSmallViewports() {
   445    var el = document.querySelector('section.slides');
   446    var transform = '';
   447    var sWidthPx = 1250;
   448    var sHeightPx = 750;
   449    var sAspectRatio = sWidthPx / sHeightPx;
   450    var wAspectRatio = window.innerWidth / window.innerHeight;
   451  
   452    if (wAspectRatio <= sAspectRatio && window.innerWidth < sWidthPx) {
   453      transform = 'scale(' + window.innerWidth / sWidthPx + ')';
   454    } else if (window.innerHeight < sHeightPx) {
   455      transform = 'scale(' + window.innerHeight / sHeightPx + ')';
   456    }
   457    el.style.transform = transform;
   458  }
   459  
   460  function addEventListeners() {
   461    document.addEventListener('keydown', handleBodyKeyDown, false);
   462    var resizeTimeout;
   463    window.addEventListener('resize', function() {
   464      // throttle resize events
   465      window.clearTimeout(resizeTimeout);
   466      resizeTimeout = window.setTimeout(function() {
   467        resizeTimeout = null;
   468        scaleSmallViewports();
   469      }, 50);
   470    });
   471  
   472    // Force reset transform property of section.slides when printing page.
   473    // Use both onbeforeprint and matchMedia for compatibility with different browsers.
   474    var beforePrint = function() {
   475      var el = document.querySelector('section.slides');
   476      el.style.transform = '';
   477    };
   478    window.onbeforeprint = beforePrint;
   479    if (window.matchMedia) {
   480      var mediaQueryList = window.matchMedia('print');
   481      mediaQueryList.addListener(function(mql) {
   482        if (mql.matches) beforePrint();
   483      });
   484    }
   485  }
   486  
   487  /* Initialization */
   488  
   489  function addFontStyle() {
   490    var el = document.createElement('link');
   491    el.rel = 'stylesheet';
   492    el.type = 'text/css';
   493    el.href =
   494      '//fonts.googleapis.com/css?family=' +
   495      'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono';
   496  
   497    document.body.appendChild(el);
   498  }
   499  
   500  function addGeneralStyle() {
   501    var el = document.createElement('link');
   502    el.rel = 'stylesheet';
   503    el.type = 'text/css';
   504    el.href = PERMANENT_URL_PREFIX + 'styles.css';
   505    document.body.appendChild(el);
   506  
   507    var el = document.createElement('meta');
   508    el.name = 'viewport';
   509    el.content = 'width=device-width,height=device-height,initial-scale=1';
   510    document.querySelector('head').appendChild(el);
   511  
   512    var el = document.createElement('meta');
   513    el.name = 'apple-mobile-web-app-capable';
   514    el.content = 'yes';
   515    document.querySelector('head').appendChild(el);
   516  
   517    scaleSmallViewports();
   518  }
   519  
   520  function handleDomLoaded() {
   521    slideEls = document.querySelectorAll('section.slides > article');
   522  
   523    setupFrames();
   524  
   525    addFontStyle();
   526    addGeneralStyle();
   527    addEventListeners();
   528  
   529    updateSlides();
   530  
   531    setupInteraction();
   532  
   533    if (
   534      window.location.hostname == 'localhost' ||
   535      window.location.hostname == '127.0.0.1' ||
   536      window.location.hostname == '::1'
   537    ) {
   538      hideHelpText();
   539    }
   540  
   541    document.body.classList.add('loaded');
   542  
   543    setupNotesSync();
   544  }
   545  
   546  function initialize() {
   547    getCurSlideFromHash();
   548  
   549    if (window['_DEBUG']) {
   550      PERMANENT_URL_PREFIX = '../';
   551    }
   552  
   553    if (window['_DCL']) {
   554      handleDomLoaded();
   555    } else {
   556      document.addEventListener('DOMContentLoaded', handleDomLoaded, false);
   557    }
   558  }
   559  
   560  // If ?debug exists then load the script relative instead of absolute
   561  if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) {
   562    document.addEventListener(
   563      'DOMContentLoaded',
   564      function() {
   565        // Avoid missing the DomContentLoaded event
   566        window['_DCL'] = true;
   567      },
   568      false
   569    );
   570  
   571    window['_DEBUG'] = true;
   572    var script = document.createElement('script');
   573    script.type = 'text/javascript';
   574    script.src = '../slides.js';
   575    var s = document.getElementsByTagName('script')[0];
   576    s.parentNode.insertBefore(script, s);
   577  
   578    // Remove this script
   579    s.parentNode.removeChild(s);
   580  } else {
   581    initialize();
   582  }
   583  
   584  /* Synchronize windows when notes are enabled */
   585  
   586  function setupNotesSync() {
   587    if (!notesEnabled) return;
   588  
   589    function setupPlayResizeSync() {
   590      var out = document.getElementsByClassName('output');
   591      for (var i = 0; i < out.length; i++) {
   592        $(out[i]).bind('resize', function(event) {
   593          if ($(event.target).hasClass('ui-resizable')) {
   594            localStorage.setItem('play-index', i);
   595            localStorage.setItem('output-style', out[i].style.cssText);
   596          }
   597        });
   598      }
   599    }
   600    function setupPlayCodeSync() {
   601      var play = document.querySelectorAll('div.playground');
   602      for (var i = 0; i < play.length; i++) {
   603        play[i].addEventListener('input', inputHandler, false);
   604  
   605        function inputHandler(e) {
   606          localStorage.setItem('play-index', i);
   607          localStorage.setItem('play-code', e.target.innerHTML);
   608        }
   609      }
   610    }
   611  
   612    setupPlayCodeSync();
   613    setupPlayResizeSync();
   614    localStorage.setItem(destSlideKey(), curSlide);
   615    window.addEventListener('storage', updateOtherWindow, false);
   616  }
   617  
   618  // An update to local storage is caught only by the other window
   619  // The triggering window does not handle any sync actions
   620  function updateOtherWindow(e) {
   621    // Ignore remove storage events which are not meant to update the other window
   622    var isRemoveStorageEvent = !e.newValue;
   623    if (isRemoveStorageEvent) return;
   624  
   625    var destSlide = localStorage.getItem(destSlideKey());
   626    while (destSlide > curSlide) {
   627      nextSlide();
   628    }
   629    while (destSlide < curSlide) {
   630      prevSlide();
   631    }
   632  
   633    updatePlay(e);
   634    updateNotes();
   635  }