<commit_before>function init () {
  var strUrl = location.search;
  var getPara, ParaVal;
  var aryPara = [];
  var timeInv;
  var start_button = document.getElementById("start");
  var stop_button = document.getElementById("stop");

  if (strUrl.indexOf("?") != -1) {
    var getSearch = strUrl.split("?");
    getPara = getSearch[ 1 ].split("&");
    for (var i = 0; i < getPara.length; i++) {
      ParaVal = getPara[ i ].split("=");
      aryPara.push(ParaVal[ 0 ]);
      aryPara[ ParaVal[ 0 ] ] = decodeURI(ParaVal[ 1 ]);
    }
    if (aryPara[ "text" ]) {
      document.getElementById("editable").innerHTML = aryPara[ "text" ];
      document.title = aryPara[ "text" ];
    }

    // Timing
    if ("countdown" in aryPara) {
      document.title = "Timer: Countdown";
      if (aryPara[ "countdown" ] === "undefined") {
        setTime(0, 0, 0, 30);
      } else {
        var days = aryPara[ "countdown" ].split("d").length > 1 ? aryPara[ "countdown" ].split("d") : [ 0, aryPara[ "countdown" ].split("d")[ 0 ] ];
        var hours = days[ 1 ].split("h").length > 1 ? days[ 1 ].split("h") : [ 0, days[ 1 ].split("h")[ 0 ] ];
        var minutes = hours[ 1 ].split("m").length > 1 ? hours[ 1 ].split("m") : [ 0, hours[ 1 ].split("m")[ 0 ] ];
        var seconds = minutes[ 1 ].split("s")[ 0 ] ? minutes[ 1 ].split("s")[ 0 ] : 0 || 0;
        setTime(parseInt(days[ 0 ]), parseInt(hours[ 0 ]), parseInt(minutes[ 0 ]), parseInt(seconds));
      }
      document.getElementById("splitDiv").style.display = "block";
      start_button.style.display = "inline-block";
      stop_button.addEventListener('click', function () {
        start_button.style.display = "inline-block";
        stop_button.style.display = "none";
        if (timeInv) {
          clearInterval(timeInv);
        }
      });
      start_button.addEventListener('click', function () {
        start_button.style.display = "none";
        stop_button.style.display = "inline-block";
        timeInv = setInterval(countdown, 1000);
      });
    } else if ("timing" in aryPara) {
      document.title = "Timer: Timing";
      setTime(0, 0, 0, 0);
      document.getElementById("splitDiv").style.display = "block";
      start_button.style.display = "inline-block";
      stop_button.addEventListener('click', function () {
        start_button.style.display = "inline-block";
        stop_button.style.display = "none";
        if (timeInv) {
          clearInterval(timeInv);
        }
      });
      start_button.addEventListener('click', function () {
        start_button.style.display = "none";
        stop_button.style.display = "inline-block";
        timeInv = setInterval(timing, 1000);
      });
    } else if ("clock" in aryPara) {
      document.title = "Clock";
      showClock();
    }

    // Must be the last one
    if (aryPara[ "title" ]) {
      document.title = aryPara[ "title" ];
    }
    // FIXME: If not use setTimeout, the "It works" will show before clock.
    setTimeout(function () {
      if (document.getElementById("editable").innerHTML == "Loading...") {
        document.getElementById("editable").innerHTML = "It works!";
      }
    }, 1000);
  }
}
function editable () {
  var div = document.getElementById("editable");
  div.contentEditable = 'true';
}

function showClock () {
  setInterval(function () {
    var currentdate = new Date();
    document.getElementById("editable").innerHTML =
      currentdate.getFullYear() + " / "
      + ("0" + currentdate.getMonth()).slice(-2) + " / "
      + ("0" + currentdate.getDate()).slice(-2) + "
"
      + ("0" + currentdate.getHours()).slice(-2) + " : "
      + ("0" + currentdate.getMinutes()).slice(-2) + " : "
      + ("0" + currentdate.getSeconds()).slice(-2);
  }, 1000);
}

function showCountdown (days, hours, mins, secs) {
  var target = document.getElementById("editable");
  target.innerHTML = "";
  if (days == 0 && hours == 0 && mins == 0 && secs == 0) {
    target.innerHTML = "Time's up!";
    document.title = "Timer: Time's up!";
    document.getElementById("splitDiv").style.display = "none";
    document.getElementById("stop").click();
    document.getElementById("start").style.display = "none";
  } else {
    setTime(days, hours, mins, secs);
  }
}

function countdown () {
  var target = document.getElementById("editable");
  var d = target.innerHTML.split("day(s), ").length > 1 ? target.innerHTML.split("day(s), ") : [ 0, target.innerHTML.split("day(s), ")[ 0 ] ];
  var time = d[ 1 ].split(" : ");
  d = d[ 0 ];
  var s = time[ 2 ];
  var m = time[ 1 ];
  var h = time[ 0 ];
  d = parseInt(d || 0);
  h = parseInt(h || 0);
  m = parseInt(m || 0);
  s = parseInt(s || 0);

  // Check time over 60s/60m/24h
  while (s >= 60) {
    m += 1;
    s -= 60;
  }
  while (m >= 60) {
    h += 1;
    m -= 60;
  }
  while (h >= 24) {
    d += 1;
    h -= 24;
  }

  if (s > 0 || m > 0 || h > 0 || d > 0) {
    s -= 1;
  }
  if (s < 0) {
    if (m > 0) {
      m -= 1;
      s += 60;
    } else {
      if (h > 0) {
        h -= 1;
        m += 59;
        s += 60;
      } else {
        if (d > 0) {
          d -= 1;
          h += 23;
          m += 59;
          s += 60;
        } else {
          d = 0;
          h = 0;
          m = 0;
          s = 0;
        }
      }
    }
  }
  showCountdown(d, h, m, s);
}

function timing () {
  var target = document.getElementById("editable");
  var data = target.innerHTML.split(" : ");
  var s = parseInt(data[ 2 ]);
  var m = parseInt(data[ 1 ]);
  var h = parseInt(data[ 0 ]);

  // Check time over 60s/60m/24h
  while (s >= 60) {
    m += 1;
    s -= 60;
  }
  while (m >= 60) {
    h += 1;
    m -= 60;
  }
  while (h >= 24) {
    d += 1;
    h -= 24;
  }

  s += 1;
  if (s >= 60) {
    s -= 60;
    m += 1;
    if (m >= 60) {
      m -= 60;
      h += 1;
      if (h >= 24) {
        h -= 24;
      }
    }
  }
  console.log(s + "," + m + "," + h);
  setTime(0, h, m, s);
}

function setTime (days, hours, mins, secs) {
  var target = document.getElementById("editable");
  target.innerHTML = "";
  if (days > 0) {
    target.innerHTML = days + "day(s), "
  }
  if (hours >= 0) {
    target.innerHTML += ("0" + hours).slice(-2) + " : ";
  }
  if (mins >= 0) {
    target.innerHTML += ("0" + mins).slice(-2) + " : ";
  }
  if (secs >= 0) {
    target.innerHTML += ("0" + secs).slice(-2);
  }
}<commit_msg>Fix bug when over 60s/60m/24h<commit_after>function init () {
  var strUrl = location.search;
  var getPara, ParaVal;
  var aryPara = [];
  var timeInv;
  var start_button = document.getElementById("start");
  var stop_button = document.getElementById("stop");

  if (strUrl.indexOf("?") != -1) {
    var getSearch = strUrl.split("?");
    getPara = getSearch[ 1 ].split("&");
    for (var i = 0; i < getPara.length; i++) {
      ParaVal = getPara[ i ].split("=");
      aryPara.push(ParaVal[ 0 ]);
      aryPara[ ParaVal[ 0 ] ] = decodeURI(ParaVal[ 1 ]);
    }
    if (aryPara[ "text" ]) {
      document.getElementById("editable").innerHTML = aryPara[ "text" ];
      document.title = aryPara[ "text" ];
    }

    // Timing
    if ("countdown" in aryPara) {
      document.title = "Timer: Countdown";
      if (aryPara[ "countdown" ] === "undefined") {
        setTime(0, 0, 0, 30);
      } else {
        var days = aryPara[ "countdown" ].split("d").length > 1 ? aryPara[ "countdown" ].split("d") : [ 0, aryPara[ "countdown" ].split("d")[ 0 ] ];
        var hours = days[ 1 ].split("h").length > 1 ? days[ 1 ].split("h") : [ 0, days[ 1 ].split("h")[ 0 ] ];
        var minutes = hours[ 1 ].split("m").length > 1 ? hours[ 1 ].split("m") : [ 0, hours[ 1 ].split("m")[ 0 ] ];
        var seconds = minutes[ 1 ].split("s")[ 0 ] ? minutes[ 1 ].split("s")[ 0 ] : 0 || 0;

        days = parseInt(days[0]);
        hours = parseInt(hours[0]);
        minutes = parseInt(minutes[0]);
        seconds = parseInt(seconds);

        // Check time over 60s/60m/24h
        while (seconds >= 60) {
          minutes += 1;
          seconds -= 60;
        }
        while (minutes >= 60) {
          hours += 1;
          minutes -= 60;
        }
        while (hours >= 24) {
          days += 1;
          hours -= 24;
        }

        setTime(days, hours, minutes, seconds);
      }
      document.getElementById("splitDiv").style.display = "block";
      start_button.style.display = "inline-block";
      stop_button.addEventListener('click', function () {
        start_button.style.display = "inline-block";
        stop_button.style.display = "none";
        if (timeInv) {
          clearInterval(timeInv);
        }
      });
      start_button.addEventListener('click', function () {
        start_button.style.display = "none";
        stop_button.style.display = "inline-block";
        timeInv = setInterval(countdown, 1000);
      });
    } else if ("timing" in aryPara) {
      document.title = "Timer: Timing";
      setTime(0, 0, 0, 0);
      document.getElementById("splitDiv").style.display = "block";
      start_button.style.display = "inline-block";
      stop_button.addEventListener('click', function () {
        start_button.style.display = "inline-block";
        stop_button.style.display = "none";
        if (timeInv) {
          clearInterval(timeInv);
        }
      });
      start_button.addEventListener('click', function () {
        start_button.style.display = "none";
        stop_button.style.display = "inline-block";
        timeInv = setInterval(timing, 1000);
      });
    } else if ("clock" in aryPara) {
      document.title = "Clock";
      showClock();
    }

    // Must be the last one
    if (aryPara[ "title" ]) {
      document.title = aryPara[ "title" ];
    }
    // FIXME: If not use setTimeout, the "It works" will show before clock.
    setTimeout(function () {
      if (document.getElementById("editable").innerHTML == "Loading...") {
        document.getElementById("editable").innerHTML = "It works!";
      }
    }, 1000);
  }
}
function editable () {
  var div = document.getElementById("editable");
  div.contentEditable = 'true';
}

function showClock () {
  setInterval(function () {
    var currentdate = new Date();
    document.getElementById("editable").innerHTML =
      currentdate.getFullYear() + " / "
      + ("0" + currentdate.getMonth()).slice(-2) + " / "
      + ("0" + currentdate.getDate()).slice(-2) + "
"
      + ("0" + currentdate.getHours()).slice(-2) + " : "
      + ("0" + currentdate.getMinutes()).slice(-2) + " : "
      + ("0" + currentdate.getSeconds()).slice(-2);
  }, 1000);
}

function showCountdown (days, hours, mins, secs) {
  var target = document.getElementById("editable");
  target.innerHTML = "";
  if (days == 0 && hours == 0 && mins == 0 && secs == 0) {
    target.innerHTML = "Time's up!";
    document.title = "Timer: Time's up!";
    document.getElementById("splitDiv").style.display = "none";
    document.getElementById("stop").click();
    document.getElementById("start").style.display = "none";
  } else {
    setTime(days, hours, mins, secs);
  }
}

function countdown () {
  var target = document.getElementById("editable");
  var d = target.innerHTML.split("day(s), ").length > 1 ? target.innerHTML.split("day(s), ") : [ 0, target.innerHTML.split("day(s), ")[ 0 ] ];
  var time = d[ 1 ].split(" : ");
  d = d[ 0 ];
  var s = time[ 2 ];
  var m = time[ 1 ];
  var h = time[ 0 ];
  d = parseInt(d || 0);
  h = parseInt(h || 0);
  m = parseInt(m || 0);
  s = parseInt(s || 0);

  // Check time over 60s/60m/24h
  while (s >= 60) {
    m += 1;
    s -= 60;
  }
  while (m >= 60) {
    h += 1;
    m -= 60;
  }
  while (h >= 24) {
    d += 1;
    h -= 24;
  }

  if (s > 0 || m > 0 || h > 0 || d > 0) {
    s -= 1;
  }
  if (s < 0) {
    if (m > 0) {
      m -= 1;
      s += 60;
    } else {
      if (h > 0) {
        h -= 1;
        m += 59;
        s += 60;
      } else {
        if (d > 0) {
          d -= 1;
          h += 23;
          m += 59;
          s += 60;
        } else {
          d = 0;
          h = 0;
          m = 0;
          s = 0;
        }
      }
    }
  }
  showCountdown(d, h, m, s);
}

function timing () {
  var target = document.getElementById("editable");
  var data = target.innerHTML.split(" : ");
  var s = parseInt(data[ 2 ]);
  var m = parseInt(data[ 1 ]);
  var h = parseInt(data[ 0 ]);

  // Check time over 60s/60m/24h
  while (s >= 60) {
    m += 1;
    s -= 60;
  }
  while (m >= 60) {
    h += 1;
    m -= 60;
  }
  while (h >= 24) {
    d += 1;
    h -= 24;
  }

  s += 1;
  if (s >= 60) {
    s -= 60;
    m += 1;
    if (m >= 60) {
      m -= 60;
      h += 1;
      if (h >= 24) {
        h -= 24;
      }
    }
  }
  console.log(s + "," + m + "," + h);
  setTime(0, h, m, s);
}

function setTime (days, hours, mins, secs) {
  var target = document.getElementById("editable");
  target.innerHTML = "";
  if (days > 0) {
    target.innerHTML = days + "day(s), "
  }
  if (hours >= 0) {
    target.innerHTML += ("0" + hours).slice(-2) + " : ";
  }
  if (mins >= 0) {
    target.innerHTML += ("0" + mins).slice(-2) + " : ";
  }
  if (secs >= 0) {
    target.innerHTML += ("0" + secs).slice(-2);
  }
}<|endoftext|>


<commit_before>function init () {
  var strUrl = location.search;
  var getPara, ParaVal;
  var aryPara = [];
  var timeInv;
  var start_button = document.getElementById("start");
  var stop_button = document.getElementById("stop");

  if (strUrl.indexOf("?") != -1) {
    var getSearch = strUrl.split("?");
    getPara = getSearch[ 1 ].split("&");
    for (var i = 0; i < getPara.length; i++) {
      ParaVal = getPara[ i ].split("=");
      aryPara.push(ParaVal[ 0 ]);
      aryPara[ ParaVal[ 0 ] ] = decodeURI(ParaVal[ 1 ]);
    }

    // Help
    if ("help" in aryPara) {
      swal({
        title: "Help!",
        text: "
Here are some useful feature:

" +
              "
" +
              "

Key	Value	Function

" +
              "
" +
              "
help	(null)	Open this page.
" +
              "
text	(text)	Open an editable pad with (text)
" +
              "
clock	(null)	Open a clock.
" +
              "
timing	(null)	Open a timer.
" +
              "
countdown	(?d?h?m?s)	Open a countdown timer.
" +
              "
image	(url)	Open an image pad with the specify image.
" +
              "
" +
              "
",
        html: true,
        type: "info",
        confirmButtonText: "Try it!"
      });
    } else {
      // Add feature here

      // Image Pad
      if ("image" in aryPara) {
        var imgUrl = decodeURI(aryPara[ "image" ]);
        imgUrl = imgUrl.replace(/%3A/g, ':').replace(/%2F/g, '/');
        if (imgUrl != "undefined") {
          document.getElementById("editable").innerHTML = ""
        } else {
          document.getElementById("editable").innerText = "404
Image Not Found!";
        }
        document.title = "Image Pad"
      }

      // Text Pad
      if ("text" in aryPara) {
        document.getElementById("editable").innerText = aryPara[ "text" ];
        document.title = "Text Pad";
      }

      // Time Pad
      if ("countdown" in aryPara) {
        document.title = "Timer: Countdown";
        if (aryPara[ "countdown" ] === "undefined") {
          setTime(0, 0, 0, 30);
        } else {
          var days = aryPara[ "countdown" ].split("d").length > 1 ? aryPara[ "countdown" ].split("d") : [ 0, aryPara[ "countdown" ].split("d")[ 0 ] ];
          var hours = days[ 1 ].split("h").length > 1 ? days[ 1 ].split("h") : [ 0, days[ 1 ].split("h")[ 0 ] ];
          var minutes = hours[ 1 ].split("m").length > 1 ? hours[ 1 ].split("m") : [ 0, hours[ 1 ].split("m")[ 0 ] ];
          var seconds = minutes[ 1 ].split("s")[ 0 ] ? minutes[ 1 ].split("s")[ 0 ] : 0 || 0;

          days = parseInt(days[ 0 ]);
          hours = parseInt(hours[ 0 ]);
          minutes = parseInt(minutes[ 0 ]);
          seconds = parseInt(seconds);

          // Check time over 60s/60m/24h
          while (seconds >= 60) {
            minutes += 1;
            seconds -= 60;
          }
          while (minutes >= 60) {
            hours += 1;
            minutes -= 60;
          }
          while (hours >= 24) {
            days += 1;
            hours -= 24;
          }

          setTime(days, hours, minutes, seconds);
        }
        document.getElementById("splitDiv").style.display = "block";
        start_button.style.display = "inline-block";
        stop_button.addEventListener('click', function () {
          start_button.style.display = "inline-block";
          stop_button.style.display = "none";
          if (timeInv) {
            clearInterval(timeInv);
          }
        });
        start_button.addEventListener('click', function () {
          start_button.style.display = "none";
          stop_button.style.display = "inline-block";
          timeInv = setInterval(countdown, 1000);
        });
      } else if ("timing" in aryPara) {
        document.title = "Timer: Timing";
        setTime(0, 0, 0, 0);
        document.getElementById("splitDiv").style.display = "block";
        start_button.style.display = "inline-block";
        stop_button.addEventListener('click', function () {
          start_button.style.display = "inline-block";
          stop_button.style.display = "none";
          if (timeInv) {
            clearInterval(timeInv);
          }
        });
        start_button.addEventListener('click', function () {
          start_button.style.display = "none";
          stop_button.style.display = "inline-block";
          timeInv = setInterval(timing, 1000);
        });
      } else if ("clock" in aryPara) {
        document.title = "Clock";
        showClock();
      }

      // Must be the last one
      if (aryPara[ "title" ]) {
        document.title = aryPara[ "title" ];
      }
    }

    // FIXME: If not use setTimeout, the "It works" will show before clock.
    setTimeout(function () {
      if (document.getElementById("editable").innerText == "Loading...") {
        document.getElementById("editable").innerText = "It works!";
      }
    }, 1000);
  }
}
function editable () {
  var div = document.getElementById("editable");
  div.contentEditable = 'true';
}

function showClock () {
  setInterval(function () {
    var currentdate = new Date();
    document.getElementById("editable").innerHTML =
      currentdate.getFullYear() + " / "
      + ("0" + currentdate.getMonth()).slice(-2) + " / "
      + ("0" + currentdate.getDate()).slice(-2) + "
"
      + ("0" + currentdate.getHours()).slice(-2) + " : "
      + ("0" + currentdate.getMinutes()).slice(-2) + " : "
      + ("0" + currentdate.getSeconds()).slice(-2);
  }, 1000);
}

function showCountdown (days, hours, mins, secs) {
  var target = document.getElementById("editable");
  target.innerText = "";
  if (days == 0 && hours == 0 && mins == 0 && secs == 0) {
    target.innerText = "Time's up!";
    document.title = "Timer: Time's up!";
    document.getElementById("splitDiv").style.display = "none";
    document.getElementById("stop").click();
    document.getElementById("start").style.display = "none";
  } else {
    setTime(days, hours, mins, secs);
  }
}

function countdown () {
  var target = document.getElementById("editable");
  var d = target.innerHTML.split("day(s),").length > 1 ? target.innerText.split("day(s),") : [ 0, target.innerText.split("day(s), ")[ 0 ] ];
  var time = d[ 1 ].split(" :");
  d = d[ 0 ];
  var s = time[ 2 ];
  var m = time[ 1 ];
  var h = time[ 0 ];
  d = parseInt(d || 0);
  h = parseInt(h || 0);
  m = parseInt(m || 0);
  s = parseInt(s || 0);

  // Check time over 60s/60m/24h
  while (s >= 60) {
    m += 1;
    s -= 60;
  }
  while (m >= 60) {
    h += 1;
    m -= 60;
  }
  while (h >= 24) {
    d += 1;
    h -= 24;
  }

  if (s > 0 || m > 0 || h > 0 || d > 0) {
    s -= 1;
  }
  if (s < 0) {
    if (m > 0) {
      m -= 1;
      s += 60;
    } else {
      if (h > 0) {
        h -= 1;
        m += 59;
        s += 60;
      } else {
        if (d > 0) {
          d -= 1;
          h += 23;
          m += 59;
          s += 60;
        } else {
          d = 0;
          h = 0;
          m = 0;
          s = 0;
        }
      }
    }
  }
  showCountdown(d, h, m, s);
}

function timing () {
  var target = document.getElementById("editable");
  var data = target.innerText.split(" :");
  var s = parseInt(data[ 2 ]);
  var m = parseInt(data[ 1 ]);
  var h = parseInt(data[ 0 ]);

  // Check time over 60s/60m/24h
  while (s >= 60) {
    m += 1;
    s -= 60;
  }
  while (m >= 60) {
    h += 1;
    m -= 60;
  }
  while (h >= 24) {
    d += 1;
    h -= 24;
  }

  s += 1;
  if (s >= 60) {
    s -= 60;
    m += 1;
    if (m >= 60) {
      m -= 60;
      h += 1;
      if (h >= 24) {
        h -= 24;
      }
    }
  }
  setTime(0, h, m, s);
}

function setTime (days, hours, mins, secs) {
  var target = document.getElementById("editable");
  target.innerText = "";
  if (days > 0) {
    target.innerText = days + "day(s), "
  }
  if (hours >= 0) {
    target.innerText += ("0" + hours).slice(-2) + " : ";
  }
  if (mins >= 0) {
    target.innerText += ("0" + mins).slice(-2) + " : ";
  }
  if (secs >= 0) {
    target.innerText += ("0" + secs).slice(-2);
  }
}<commit_msg>Update timer<commit_after>function init () {
  var strUrl = location.search;
  var getPara, ParaVal;
  var aryPara = [];
  var timeInv;
  var start_button = document.getElementById("start");
  var stop_button = document.getElementById("stop");

  if (strUrl.indexOf("?") != -1) {
    var getSearch = strUrl.split("?");
    getPara = getSearch[ 1 ].split("&");
    for (var i = 0; i < getPara.length; i++) {
      ParaVal = getPara[ i ].split("=");
      aryPara.push(ParaVal[ 0 ]);
      aryPara[ ParaVal[ 0 ] ] = decodeURI(ParaVal[ 1 ]);
    }

    // Help
    if ("help" in aryPara) {
      swal({
        title: "Help!",
        text: "
Here are some useful feature:

" +
              "
" +
              "

Key	Value	Function

" +
              "
" +
              "
help	(null)	Open this page.
" +
              "
text	(text)	Open an editable pad with (text)
" +
              "
clock	(null)	Open a clock.
" +
              "
timing	(null)	Open a timer.
" +
              "
countdown	(?d?h?m?s)	Open a countdown timer.
" +
              "
image	(url)	Open an image pad with the specify image.
" +
              "
" +
              "
",
        html: true,
        type: "info",
        confirmButtonText: "Try it!"
      });
    } else {
      // Add feature here

      // Image Pad
      if ("image" in aryPara) {
        var imgUrl = decodeURI(aryPara[ "image" ]);
        imgUrl = imgUrl.replace(/%3A/g, ':').replace(/%2F/g, '/');
        if (imgUrl != "undefined") {
          document.getElementById("editable").innerHTML = ""
        } else {
          document.getElementById("editable").innerText = "404
Image Not Found!";
        }
        document.title = "Image Pad"
      }

      // Text Pad
      if ("text" in aryPara) {
        document.getElementById("editable").innerText = aryPara[ "text" ];
        document.title = "Text Pad";
      }

      // Time Pad
      if ("countdown" in aryPara) {
        document.title = "Timer: Countdown";
        if (aryPara[ "countdown" ] === "undefined") {
          setTime(0, 0, 0, 30);
        } else {
          var days = aryPara[ "countdown" ].split("d").length > 1 ? aryPara[ "countdown" ].split("d") : [ 0, aryPara[ "countdown" ].split("d")[ 0 ] ];
          var hours = days[ 1 ].split("h").length > 1 ? days[ 1 ].split("h") : [ 0, days[ 1 ].split("h")[ 0 ] ];
          var minutes = hours[ 1 ].split("m").length > 1 ? hours[ 1 ].split("m") : [ 0, hours[ 1 ].split("m")[ 0 ] ];
          var seconds = minutes[ 1 ].split("s")[ 0 ] ? minutes[ 1 ].split("s")[ 0 ] : 0 || 0;

          days = parseInt(days[ 0 ]);
          hours = parseInt(hours[ 0 ]);
          minutes = parseInt(minutes[ 0 ]);
          seconds = parseInt(seconds);

          // Check time over 60s/60m/24h
          while (seconds >= 60) {
            minutes += 1;
            seconds -= 60;
          }
          while (minutes >= 60) {
            hours += 1;
            minutes -= 60;
          }
          while (hours >= 24) {
            days += 1;
            hours -= 24;
          }

          setTime(days, hours, minutes, seconds);
        }
        document.getElementById("splitDiv").style.display = "block";
        start_button.style.display = "inline-block";
        stop_button.addEventListener('click', function () {
          start_button.style.display = "inline-block";
          stop_button.style.display = "none";
          if (timeInv) {
            clearInterval(timeInv);
          }
        });
        start_button.addEventListener('click', function () {
          start_button.style.display = "none";
          stop_button.style.display = "inline-block";
          timeInv = setInterval(countdown, 1000);
        });
      } else if ("timing" in aryPara) {
        document.title = "Timer: Timing";
        setTime(0, 0, 0, 0);
        document.getElementById("splitDiv").style.display = "block";
        start_button.style.display = "inline-block";
        stop_button.addEventListener('click', function () {
          start_button.style.display = "inline-block";
          stop_button.style.display = "none";
          if (timeInv) {
            clearInterval(timeInv);
          }
        });
        start_button.addEventListener('click', function () {
          start_button.style.display = "none";
          stop_button.style.display = "inline-block";
          timeInv = setInterval(timing, 1000);
        });
      } else if ("clock" in aryPara) {
        document.title = "Clock";
        showClock();
      }

      // Must be the last one
      if (aryPara[ "title" ]) {
        document.title = aryPara[ "title" ];
      }
    }

    // FIXME: If not use setTimeout, the "It works" will show before clock.
    setTimeout(function () {
      if (document.getElementById("editable").innerText == "Loading...") {
        document.getElementById("editable").innerText = "It works!";
      }
    }, 1000);
  }
}
function editable () {
  var div = document.getElementById("editable");
  div.contentEditable = 'true';
}

function showClock () {
  setInterval(function () {
    var currentdate = new Date();
    document.getElementById("editable").innerHTML =
      currentdate.getFullYear() + " / "
      + ("0" + currentdate.getMonth()).slice(-2) + " / "
      + ("0" + currentdate.getDate()).slice(-2) + "
"
      + ("0" + currentdate.getHours()).slice(-2) + " : "
      + ("0" + currentdate.getMinutes()).slice(-2) + " : "
      + ("0" + currentdate.getSeconds()).slice(-2);
  }, 1000);
}

function showCountdown (days, hours, mins, secs) {
  var target = document.getElementById("editable");
  target.innerText = "";
  if (days == 0 && hours == 0 && mins == 0 && secs == 0) {
    target.innerText = "Time's up!";
    document.title = "Timer: Time's up!";
    document.getElementById("splitDiv").style.display = "none";
    document.getElementById("stop").click();
    document.getElementById("start").style.display = "none";
  } else {
    setTime(days, hours, mins, secs);
  }
}

function countdown () {
  var target = document.getElementById("editable");
  var d = target.innerHTML.split("day(s), ").length > 1 ? target.innerHTML.split("day(s), ") : [ 0, target.innerText.split("day(s), ")[ 0 ] ];
  var time = d[ 1 ].split(" : ");
  d = d[ 0 ];
  var s = time[ 2 ];
  var m = time[ 1 ];
  var h = time[ 0 ];
  d = parseInt(d || 0);
  h = parseInt(h || 0);
  m = parseInt(m || 0);
  s = parseInt(s || 0);

  // Check time over 60s/60m/24h
  while (s >= 60) {
    m += 1;
    s -= 60;
  }
  while (m >= 60) {
    h += 1;
    m -= 60;
  }
  while (h >= 24) {
    d += 1;
    h -= 24;
  }

  if (s > 0 || m > 0 || h > 0 || d > 0) {
    s -= 1;
  }
  if (s < 0) {
    if (m > 0) {
      m -= 1;
      s += 60;
    } else {
      if (h > 0) {
        h -= 1;
        m += 59;
        s += 60;
      } else {
        if (d > 0) {
          d -= 1;
          h += 23;
          m += 59;
          s += 60;
        } else {
          d = 0;
          h = 0;
          m = 0;
          s = 0;
        }
      }
    }
  }
  showCountdown(d, h, m, s);
}

function timing () {
  var target = document.getElementById("editable");
  var data = target.innerHTML.split(" : ");
  var s = parseInt(data[ 2 ]);
  var m = parseInt(data[ 1 ]);
  var h = parseInt(data[ 0 ]);

  // Check time over 60s/60m/24h
  while (s >= 60) {
    m += 1;
    s -= 60;
  }
  while (m >= 60) {
    h += 1;
    m -= 60;
  }
  while (h >= 24) {
    d += 1;
    h -= 24;
  }

  s += 1;
  if (s >= 60) {
    s -= 60;
    m += 1;
    if (m >= 60) {
      m -= 60;
      h += 1;
      if (h >= 24) {
        h -= 24;
      }
    }
  }
  setTime(0, h, m, s);
}

function setTime (days, hours, mins, secs) {
  var target = document.getElementById("editable");
  target.innerHTML = "";
  if (days > 0) {
    target.innerHTML = days + "day(s), "
  }
  if (hours >= 0) {
    target.innerHTML += ("0" + hours).slice(-2) + " : ";
  }
  if (mins >= 0) {
    target.innerHTML += ("0" + mins).slice(-2) + " : ";
  }
  if (secs >= 0) {
    target.innerHTML += ("0" + secs).slice(-2);
  }
}<|endoftext|>


<commit_before>/**
	generate, animate, and display a paddler

	paddlers are procedurally-generated creatures with
	bilateral symmetry. flaps of tissue that look like
	tentacles or wings extend from cylindrical bodies.
	continuous paddling motions of these flaps permits
	them to move through the air.

	@namespace GAS
	@class paddler
**/

GAS.paddler = {

	DRAW_RADIUS: 1,
	SORT_ORDER: 2,

	VIEW_ANGLE: Math.cos(120 * Math.PI / 180),

	SHAPE: [0, 0.25, 0.5, 0.8, 0.9, 0.8, 0.7, 0.4],

	IDLE_FLAP: 0.05,
	SLOW_FLAP: 0.25,
	FAST_FLAP: 0.65,

	SLOW_SPEED: 1,
	FAST_SPEED: 10,

	colorBase: 128,
	rng: SOAR.random.create(333221),

	/**
		create and init objects usable by all paddler instances

		@method init
	**/

	init: function() {
		this.shader = SOAR.shader.create(
			GAS.display,
			SOAR.textOf("vs-paddler"), SOAR.textOf("fs-paddler"),
			["position", "texturec"],
			["projector", "modelview", "rotations", "center", "wing", "mouth"],
			["skin"]
		);

		this.mesh = this.makeMesh();
		this.skin = this.makeSkin();
	},

	/**
		generate and return a new paddler object

		@method create
		@param info object, describes appearance and position
		@return object, the new paddler
	**/

	create: function(info) {
		var o = Object.create(GAS.paddler);
		var p = info.position;

		o.position = SOAR.vector.create(p.x, p.y, p.z);
		o.velocity = SOAR.vector.create();
		o.rotator = SOAR.rotator.create();

		o.speed = 0;
		o.haste = 0;

		o.skin = this.makeSkin(info.coat, info.spot, info.seed);

		o.wing = Math.random();
		o.mouth = 0.25 * Math.PI;

		o.scratch = {
			p: SOAR.vector.create(),
			r: SOAR.vector.create()
		};

		return o;
	},

	/**
		generate random skin texture

		@method makeSkin
		@return the texture object
	**/

	makeSkin: function(coat, spot, seed) {
		var ctx = GAS.texture.context;
		var rng = SOAR.random.create(seed);
		var w = 256;
		var h = 256;
		var hw = w * 0.5;
		var hh = h * 0.5;
		var i, x, y, s, hs;

		// fill in the coat color
		ctx.fillStyle = coat;
		ctx.fillRect(0, 0, w, h);

		// top half is darker
		ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
		ctx.fillRect(0, 0, hw, h);

		// add symmetric spots
		ctx.fillStyle = spot;
		ctx.strokeStyle = "rgb(0, 0, 0)";
		ctx.lineWidth = 2;
		for (i = 0; i < 100; i++) {
			s = this.rng.get(4, 16);
			hs = s / 2;
			x = this.rng.get(hs, hw - hs);
			y = this.rng.get(hs, h - hs - 20);

			// top
			ctx.beginPath();
			ctx.arc(x, y, hs, 0, SOAR.PIMUL2, false);
			ctx.fill();
			ctx.stroke();

			// bottom
			ctx.beginPath();
			ctx.arc(hw + x, y, hs, 0, SOAR.PIMUL2, false);
			ctx.fill();
			ctx.stroke();
		}

		// add mouth stripe
		ctx.fillStyle = "rgb(0, 0, 0)";
		ctx.fillRect(0, h - 4, w, h);

		// add eye spot to top only
		ctx.fillStyle = "rgb(255, 255, 255)";
		ctx.beginPath();
		ctx.arc(hw - 20, h - 15, 5, 0, SOAR.PIMUL2, false);
		ctx.fill();
		ctx.stroke();
		ctx.fillStyle = "rgb(0, 0, 0)";
		ctx.beginPath();
		ctx.arc(hw - 20, h - 15, 2, 0, SOAR.PIMUL2, false);
		ctx.fill();

		return SOAR.texture.create(
			GAS.display,
			ctx.getImageData(0, 0, w, h)
		);
	},

	/**
		generate a paddler model mesh

		@method makeMesh
		@return object, the generated mesh
	**/

	makeMesh: function() {
		var i, r;

		var mesh = SOAR.mesh.create(GAS.display)
		mesh.add(this.shader.position, 3);
		mesh.add(this.shader.texturec, 2);

		var shaper = SOAR.noise1D.create(0, 0.5, 8, 8);
		shaper.interpolate = SOAR.interpolator.linear;
		shaper.map = new Float32Array(this.SHAPE);

		SOAR.subdivide(6, -0.5, -0.5, 0.5, 0.5,
			function(x0, z0, x1, z1, x2, z2) {
				var y0, y1, y2;
				var tz0, tz1, tz2;
				var tx0, tx1, tx2;
				var r0, r1, r2;

				tz0 = z0 + 0.5;
				tz1 = z1 + 0.5;
				tz2 = z2 + 0.5;

				r0 = Math.sqrt(shaper.get(tz0));
				r1 = Math.sqrt(shaper.get(tz1));
				r2 = Math.sqrt(shaper.get(tz2));

				y0 = 0.25 * r0 * Math.cos(x0 * Math.PI);
				y1 = 0.25 * r1 * Math.cos(x1 * Math.PI);
				y2 = 0.25 * r2 * Math.cos(x2 * Math.PI);

				x0 = 1.5 * x0 * r0;
				x1 = 1.5 * x1 * r1;
				x2 = 1.5 * x2 * r2;

				// cut x texture off at 0.49 to prevent
				// top & bottom textures crossing over
				tx0 = Math.min(0.5 - Math.abs(x0), 0.49);
				tx1 = Math.min(0.5 - Math.abs(x1), 0.49);
				tx2 = Math.min(0.5 - Math.abs(x2), 0.49);

				// paddler mouth should tuck inwards
				if (tz0 < 0.001)
					z0 += 0.05;
				if (tz1 < 0.001)
					z1 += 0.05;
				if (tz2 < 0.001)
					z2 += 0.05;

				mesh.set(x0, y0, z0, tx0, tz0);
				mesh.set(x1, y1, z1, tx1, tz1);
				mesh.set(x2, y2, z2, tx2, tz2);

				y0 = -0.5 * y0;
				y1 = -0.5 * y1;
				y2 = -0.5 * y2;

				mesh.set(x0, y0, z0, tx0 + 0.5, tz0);
				mesh.set(x2, y2, z2, tx2 + 0.5, tz2);
				mesh.set(x1, y1, z1, tx1 + 0.5, tz1);
			}
		);

		mesh.build();

		return mesh;
	},

	/**
		update kinematics

		@method update
	**/

	update: function() {
		var s = this.scratch;
		var m = this.model;
		var dt = SOAR.interval * 0.001;
		var ds;

		// haste tells us how we're supposed to be moving
		switch(this.haste) {

		case 0:

			if (this.speed > 0) {
				// decelerate based on how fast we're going
				ds = (this.speed > this.SLOW_SPEED ? this.FAST_SPEED : this.SLOW_SPEED) * 2 * dt;
				this.speed = Math.max(0, this.speed - ds);
			}
			this.wing += this.IDLE_FLAP;
			break;

		case 1:

			if (this.speed < this.SLOW_SPEED) {
				// accelerate from zero
				ds = this.SLOW_SPEED * 2 * dt;
				this.speed = Math.min(this.SLOW_SPEED, this.speed + ds);
			} else if (this.speed > this.SLOW_SPEED) {
				// decelerate from faster speed
				ds = this.FAST_SPEED * 2 * dt;
				this.speed = Math.max(this.SLOW_SPEED, this.speed - ds);
			}
			this.wing += this.SLOW_FLAP;
			break;

		case 2:

			if (this.speed < this.FAST_SPEED) {
				// accelerate
				ds = this.FAST_SPEED * 2 * dt;
				this.speed = Math.min(this.FAST_SPEED, this.speed + ds);
				this.wing += this.FAST_FLAP;
			} else {
				this.wing += this.SLOW_FLAP;
			}
			break;
		}

		this.wing = this.wing % SOAR.PIMUL2;

		this.velocity.copy(this.rotator.front).mul(this.speed * dt);
		this.position.add(this.velocity);
	},

	/**
		orient "front" to a particular vector

		note: p must be normalized!

		@method pointTo
		@param p object, vector to align front
		@param t number, rate at which to align
	**/

	pointTo: function(p, t) {
		var r = this.rotator;
		var q = r.scratch.q;
		var fr = this.rotator.front;
		fr.cross(p).neg();
		q.setFromAxisAngle(fr.x, fr.y, fr.z, t).norm();
		r.product.mul(q).norm();
		// reduces roll and regenerates vectors/matrixes
		r.turn(0, 0, this.rotator.right.y);
	},

	/**
		determines if the paddler can see another paddler

		@method isSeeing
		@param o object, the second paddler object
		@return true if the other paddler is in FOV
	**/

	isSeeing: function(o) {
		var eye = this.scratch.p;
		var ray = this.scratch.r;

		eye.copy(this.rotator.front).add(this.rotator.up).mul(0.5).norm();
		ray.copy(this.position).sub(o.position).norm();
		return eye.dot(ray) < this.VIEW_ANGLE;
	},

	/**
		draw the paddlers

		@method draw
	**/

	draw: function() {
		var gl = GAS.display.gl;
		var shader = this.shader;
		var camera = GAS.player.camera;

		if (GAS.map.lastDraw !== shader) {
			gl.disable(gl.BLEND);
			gl.enable(gl.CULL_FACE);
			gl.cullFace(gl.BACK);
			shader.activate();
			gl.uniformMatrix4fv(shader.projector, false, camera.matrix.projector);
			gl.uniformMatrix4fv(shader.modelview, false, camera.matrix.modelview);
			GAS.map.lastDraw = shader;
		}

		gl.uniformMatrix4fv(shader.rotations, false, this.rotator.matrix.transpose);
		gl.uniform3f(shader.center, this.position.x, this.position.y, this.position.z);
		gl.uniform1f(shader.wing, this.wing);
		gl.uniform1f(shader.mouth, this.mouth);
		this.skin.bind(0, shader.skin);
		this.mesh.draw();
	}

};

<commit_msg>Alters shading of paddler textures.<commit_after>/**
	generate, animate, and display a paddler

	paddlers are procedurally-generated creatures with
	bilateral symmetry. flaps of tissue that look like
	tentacles or wings extend from cylindrical bodies.
	continuous paddling motions of these flaps permits
	them to move through the air.

	@namespace GAS
	@class paddler
**/

GAS.paddler = {

	DRAW_RADIUS: 1,
	SORT_ORDER: 2,

	VIEW_ANGLE: Math.cos(120 * Math.PI / 180),

	SHAPE: [0, 0.25, 0.5, 0.8, 0.9, 0.8, 0.7, 0.4],

	IDLE_FLAP: 0.05,
	SLOW_FLAP: 0.25,
	FAST_FLAP: 0.65,

	SLOW_SPEED: 1,
	FAST_SPEED: 10,

	colorBase: 128,
	rng: SOAR.random.create(333221),

	/**
		create and init objects usable by all paddler instances

		@method init
	**/

	init: function() {
		this.shader = SOAR.shader.create(
			GAS.display,
			SOAR.textOf("vs-paddler"), SOAR.textOf("fs-paddler"),
			["position", "texturec"],
			["projector", "modelview", "rotations", "center", "wing", "mouth"],
			["skin"]
		);

		this.mesh = this.makeMesh();
		this.skin = this.makeSkin();
	},

	/**
		generate and return a new paddler object

		@method create
		@param info object, describes appearance and position
		@return object, the new paddler
	**/

	create: function(info) {
		var o = Object.create(GAS.paddler);
		var p = info.position;

		o.position = SOAR.vector.create(p.x, p.y, p.z);
		o.velocity = SOAR.vector.create();
		o.rotator = SOAR.rotator.create();

		o.speed = 0;
		o.haste = 0;

		o.skin = this.makeSkin(info.coat, info.spot, info.seed);

		o.wing = Math.random();
		o.mouth = 0.25 * Math.PI;

		o.scratch = {
			p: SOAR.vector.create(),
			r: SOAR.vector.create()
		};

		return o;
	},

	/**
		generate random skin texture

		@method makeSkin
		@return the texture object
	**/

	makeSkin: function(coat, spot, seed) {
		var ctx = GAS.texture.context;
		var rng = SOAR.random.create(seed);
		var w = 256;
		var h = 256;
		var hw = w * 0.5;
		var hh = h * 0.5;
		var i, x, y, s, hs, lg;

		// fill in the coat color
		ctx.fillStyle = coat;
		ctx.fillRect(0, 0, w, h);

		// add a shade gradient
		lg = ctx.createLinearGradient(0, 0, w, 0);
		lg.addColorStop(0, "rgba(0, 0, 0, 1)");
		lg.addColorStop(0.5, "rgba(0, 0, 0, 0)");
		lg.addColorStop(1, "rgba(0, 0, 0, 1)");
		ctx.fillStyle = lg;
		ctx.fillRect(0, 0, w, h);

		// add symmetric spots
		ctx.fillStyle = spot;
		ctx.strokeStyle = "rgb(0, 0, 0)";
		ctx.lineWidth = 2;
		for (i = 0; i < 100; i++) {
			s = this.rng.get(4, 16);
			hs = s / 2;
			x = this.rng.get(hs, hw - hs);
			y = this.rng.get(hs, h - hs - 20);

			// top
			ctx.beginPath();
			ctx.arc(x, y, hs, 0, SOAR.PIMUL2, false);
			ctx.fill();
			ctx.stroke();

			// bottom
			ctx.beginPath();
			ctx.arc(w - x, y, hs, 0, SOAR.PIMUL2, false);
			ctx.fill();
			ctx.stroke();
		}

		// add mouth stripe
		ctx.fillStyle = "rgb(0, 0, 0)";
		ctx.fillRect(0, h - 4, w, h);

		// add eye spot to top only
		ctx.fillStyle = "rgb(255, 255, 255)";
		ctx.beginPath();
		ctx.arc(hw - 20, h - 15, 5, 0, SOAR.PIMUL2, false);
		ctx.fill();
		ctx.stroke();
		ctx.fillStyle = "rgb(0, 0, 0)";
		ctx.beginPath();
		ctx.arc(hw - 20, h - 15, 2, 0, SOAR.PIMUL2, false);
		ctx.fill();

		return SOAR.texture.create(
			GAS.display,
			ctx.getImageData(0, 0, w, h)
		);
	},

	/**
		generate a paddler model mesh

		@method makeMesh
		@return object, the generated mesh
	**/

	makeMesh: function() {
		var i, r;

		var mesh = SOAR.mesh.create(GAS.display)
		mesh.add(this.shader.position, 3);
		mesh.add(this.shader.texturec, 2);

		var shaper = SOAR.noise1D.create(0, 0.5, 8, 8);
		shaper.interpolate = SOAR.interpolator.linear;
		shaper.map = new Float32Array(this.SHAPE);

		SOAR.subdivide(6, -0.5, -0.5, 0.5, 0.5,
			function(x0, z0, x1, z1, x2, z2) {
				var y0, y1, y2;
				var tz0, tz1, tz2;
				var tx0, tx1, tx2;
				var r0, r1, r2;

				tz0 = z0 + 0.5;
				tz1 = z1 + 0.5;
				tz2 = z2 + 0.5;

				r0 = Math.sqrt(shaper.get(tz0));
				r1 = Math.sqrt(shaper.get(tz1));
				r2 = Math.sqrt(shaper.get(tz2));

				y0 = 0.25 * r0 * Math.cos(x0 * Math.PI);
				y1 = 0.25 * r1 * Math.cos(x1 * Math.PI);
				y2 = 0.25 * r2 * Math.cos(x2 * Math.PI);

				x0 = 1.5 * x0 * r0;
				x1 = 1.5 * x1 * r1;
				x2 = 1.5 * x2 * r2;

				tx0 = 0.5 - Math.abs(x0);
				tx1 = 0.5 - Math.abs(x1);
				tx2 = 0.5 - Math.abs(x2);

				// paddler mouth should tuck inwards
				if (tz0 < 0.001)
					z0 += 0.05;
				if (tz1 < 0.001)
					z1 += 0.05;
				if (tz2 < 0.001)
					z2 += 0.05;

				mesh.set(x0, y0, z0, tx0, tz0);
				mesh.set(x1, y1, z1, tx1, tz1);
				mesh.set(x2, y2, z2, tx2, tz2);

				y0 = -0.5 * y0;
				y1 = -0.5 * y1;
				y2 = -0.5 * y2;

				mesh.set(x0, y0, z0, 1 - tx0, tz0);
				mesh.set(x2, y2, z2, 1 - tx2, tz2);
				mesh.set(x1, y1, z1, 1 - tx1, tz1);
			}
		);

		mesh.build();

		return mesh;
	},

	/**
		update kinematics

		@method update
	**/

	update: function() {
		var s = this.scratch;
		var m = this.model;
		var dt = SOAR.interval * 0.001;
		var ds;

		// haste tells us how we're supposed to be moving
		switch(this.haste) {

		case 0:

			if (this.speed > 0) {
				// decelerate based on how fast we're going
				ds = (this.speed > this.SLOW_SPEED ? this.FAST_SPEED : this.SLOW_SPEED) * 2 * dt;
				this.speed = Math.max(0, this.speed - ds);
			}
			this.wing += this.IDLE_FLAP;
			break;

		case 1:

			if (this.speed < this.SLOW_SPEED) {
				// accelerate from zero
				ds = this.SLOW_SPEED * 2 * dt;
				this.speed = Math.min(this.SLOW_SPEED, this.speed + ds);
			} else if (this.speed > this.SLOW_SPEED) {
				// decelerate from faster speed
				ds = this.FAST_SPEED * 2 * dt;
				this.speed = Math.max(this.SLOW_SPEED, this.speed - ds);
			}
			this.wing += this.SLOW_FLAP;
			break;

		case 2:

			if (this.speed < this.FAST_SPEED) {
				// accelerate
				ds = this.FAST_SPEED * 2 * dt;
				this.speed = Math.min(this.FAST_SPEED, this.speed + ds);
				this.wing += this.FAST_FLAP;
			} else {
				this.wing += this.SLOW_FLAP;
			}
			break;
		}

		this.wing = this.wing % SOAR.PIMUL2;

		this.velocity.copy(this.rotator.front).mul(this.speed * dt);
		this.position.add(this.velocity);
	},

	/**
		orient "front" to a particular vector

		note: p must be normalized!

		@method pointTo
		@param p object, vector to align front
		@param t number, rate at which to align
	**/

	pointTo: function(p, t) {
		var r = this.rotator;
		var q = r.scratch.q;
		var fr = this.rotator.front;
		fr.cross(p).neg();
		q.setFromAxisAngle(fr.x, fr.y, fr.z, t).norm();
		r.product.mul(q).norm();
		// reduces roll and regenerates vectors/matrixes
		r.turn(0, 0, this.rotator.right.y);
	},

	/**
		determines if the paddler can see another paddler

		@method isSeeing
		@param o object, the second paddler object
		@return true if the other paddler is in FOV
	**/

	isSeeing: function(o) {
		var eye = this.scratch.p;
		var ray = this.scratch.r;

		eye.copy(this.rotator.front).add(this.rotator.up).mul(0.5).norm();
		ray.copy(this.position).sub(o.position).norm();
		return eye.dot(ray) < this.VIEW_ANGLE;
	},

	/**
		draw the paddlers

		@method draw
	**/

	draw: function() {
		var gl = GAS.display.gl;
		var shader = this.shader;
		var camera = GAS.player.camera;

		if (GAS.map.lastDraw !== shader) {
			gl.disable(gl.BLEND);
			gl.enable(gl.CULL_FACE);
			gl.cullFace(gl.BACK);
			shader.activate();
			gl.uniformMatrix4fv(shader.projector, false, camera.matrix.projector);
			gl.uniformMatrix4fv(shader.modelview, false, camera.matrix.modelview);
			GAS.map.lastDraw = shader;
		}

		gl.uniformMatrix4fv(shader.rotations, false, this.rotator.matrix.transpose);
		gl.uniform3f(shader.center, this.position.x, this.position.y, this.position.z);
		gl.uniform1f(shader.wing, this.wing);
		gl.uniform1f(shader.mouth, this.mouth);
		this.skin.bind(0, shader.skin);
		this.mesh.draw();
	}

};

<|endoftext|>


<commit_before>/* globals selectedValue, correct,
    initializeClock, ChessBoard, $, problems, renderFeature, updateUrlWithState, getParameterByName,
    description, restart, Chess, kg_puzzles, mcdonnell_puzzles, caro_puzzles ,endgame_puzzles, rook_puzzles, selectPuzzle, highlight, clearHighlight, highlightControlledSquares, all_puzzles */

var all_scores = { results: [] };

'use strict';

function initialiseHighScores(all_puzzles, scores) {
    all_scores = scores;
    console.log("initialiseHighScores");
    console.log(all_scores);
    var $highscoretable = $('#highscoretable');
    $highscoretable.empty();
    $.each(all_puzzles, function(key) {
        $highscoretable.append(highscore(key, all_puzzles, all_scores));
    });
}

function highscore(key, all_puzzles, all_scores) {
    var scores = playerScores(key, all_scores);
    var blank = '

';
    var result = '

' + description(key) + '
';
    if (scores.length > 0) {
        result += '
' + playerLink(scores[0].player) + '

' + scores[0].score + '
';
    }
    else {
        result += blank;
    }
    if (scores.length > 1) {
        result += '
' + playerLink(scores[1].player) + '

' + scores[1].score + '
';
    }
    else {
        result += blank;
    }
    if (scores.length > 2) {
        result += '
' + playerLink(scores[2].player) + '

' + scores[2].score + '
';
    }
    else {
        result += blank;
    }
    result += '
';
    return result;
}

function playerLink(player) {
    return '' + player + '';
}

function playerScores(key, all_scores) {
    return all_scores.results.filter(row => row.game === key);
}

var hss = "https://sleepy-reaches-61664.herokuapp.com";

function fetchHighScores() {
    console.log("fetch");
    $.ajax({
        url: hss + "/highscore"
    }).then(function(data) {
        console.log(data);
        initialiseHighScores(all_puzzles, data);
    });
}

function checkHiscore() {
    var scoresInCategory = playerScores(selectedValue, all_scores);
    console.log("check high score: " + correct + " in " + scoresInCategory);
    if (scoresInCategory.filter(x => x.score > correct).length > 0) {
        return;
    }
    showHighScoreForm(selectedValue, correct);
}

function showHighScoreForm(category, correct) {
    $('.newhighscore').css("visibility", "visible");
    $('.outcome').css("visibility", "hidden");
    document.getElementById("blunder").disabled = true;
    document.getElementById("next").disabled = true;
    $("#sendscore").removeAttr("click");
    $("#sendscore").removeAttr("onclick");
    $("#sendscore").prop('onclick',null).off('click');
    $('#sendscore').on('click', function f() {
        console.log("clicked");
        submitHighscore(category, correct);
    });

}

function submitHighscore(category, correct) {
    console.log("submit highscore");
    $('.newhighscore').css("visibility", "hidden");
    updateHighscores(category, $('#player').val(), correct);
    restart();
}

function updateHighscores(game, player, score) {
    console.log("update");
    if (!/^[a-zA-Z0-9_\-]+$/.test(player)) {
        console.log("invalid username " + player);
        return;
    }

    $.ajax({
        url: hss + "/set" +
            "?game=" + encodeURIComponent(game) +
            "&player=" + encodeURIComponent(player) +
            "&score=" + encodeURIComponent(score)
    }).then(function(data) {
        console.log(data);
        fetchHighScores();
    });
}

/**
 * 60 second timer
 */
function initializeTimer(id, endtime) {
    var timer = document.getElementById(id);
    var timeinterval = setInterval(function() {
        var t = endtime - Date.now();
        var seconds = (t / 1000);
        seconds = (seconds % 60).toFixed(0);
        timer.innerHTML = "Clock: " + pad(seconds, 2) + " s";
        if (t <= 0) {
            clearInterval(timeinterval);
            timer.innerHTML = "Clock: 0.0 s";
            checkHiscore();
        }
    }, 300);
}

function pad(num, size) {
    var s = "000000000" + num;
    return s.substr(s.length - size);
}

function startHighscoreAttempt() {
    restart();
    initializeTimer('timer', Date.now() + 60 * 1000);
}
<commit_msg>fix 2/3 pos new highscore<commit_after>/* globals selectedValue, correct,
    initializeClock, ChessBoard, $, problems, renderFeature, updateUrlWithState, getParameterByName,
    description, restart, Chess, kg_puzzles, mcdonnell_puzzles, caro_puzzles ,endgame_puzzles, rook_puzzles, selectPuzzle, highlight, clearHighlight, highlightControlledSquares, all_puzzles */

var all_scores = { results: [] };

'use strict';

function initialiseHighScores(all_puzzles, scores) {
    all_scores = scores;
    console.log("initialiseHighScores");
    console.log(all_scores);
    var $highscoretable = $('#highscoretable');
    $highscoretable.empty();
    $.each(all_puzzles, function(key) {
        $highscoretable.append(highscore(key, all_puzzles, all_scores));
    });
}

function highscore(key, all_puzzles, all_scores) {
    var scores = playerScores(key, all_scores);
    var blank = '

';
    var result = '

' + description(key) + '
';
    if (scores.length > 0) {
        result += '
' + playerLink(scores[0].player) + '

' + scores[0].score + '
';
    }
    else {
        result += blank;
    }
    if (scores.length > 1) {
        result += '
' + playerLink(scores[1].player) + '

' + scores[1].score + '
';
    }
    else {
        result += blank;
    }
    if (scores.length > 2) {
        result += '
' + playerLink(scores[2].player) + '

' + scores[2].score + '
';
    }
    else {
        result += blank;
    }
    result += '
';
    return result;
}

function playerLink(player) {
    return '' + player + '';
}

function playerScores(key, all_scores) {
    return all_scores.results.filter(row => row.game === key);
}

var hss = "https://sleepy-reaches-61664.herokuapp.com";

function fetchHighScores() {
    console.log("fetch");
    $.ajax({
        url: hss + "/highscore"
    }).then(function(data) {
        console.log(data);
        initialiseHighScores(all_puzzles, data);
    });
}

function checkHiscore() {
    var scoresInCategory = playerScores(selectedValue, all_scores);
    console.log("check high score: " + correct + " in " + scoresInCategory);
    if (scoresInCategory.filter(x => x.score > correct).length > 2) {
        return;
    }
    showHighScoreForm(selectedValue, correct);
}

function showHighScoreForm(category, correct) {
    $('.newhighscore').css("visibility", "visible");
    $('.outcome').css("visibility", "hidden");
    document.getElementById("blunder").disabled = true;
    document.getElementById("next").disabled = true;
    $("#sendscore").removeAttr("click");
    $("#sendscore").removeAttr("onclick");
    $("#sendscore").prop('onclick',null).off('click');
    $('#sendscore').on('click', function f() {
        console.log("clicked");
        submitHighscore(category, correct);
    });

}

function submitHighscore(category, correct) {
    console.log("submit highscore");
    $('.newhighscore').css("visibility", "hidden");
    updateHighscores(category, $('#player').val(), correct);
    restart();
}

function updateHighscores(game, player, score) {
    console.log("update");
    if (!/^[a-zA-Z0-9_\-]+$/.test(player)) {
        console.log("invalid username " + player);
        return;
    }

    $.ajax({
        url: hss + "/set" +
            "?game=" + encodeURIComponent(game) +
            "&player=" + encodeURIComponent(player) +
            "&score=" + encodeURIComponent(score)
    }).then(function(data) {
        console.log(data);
        fetchHighScores();
    });
}

/**
 * 60 second timer
 */
function initializeTimer(id, endtime) {
    var timer = document.getElementById(id);
    var timeinterval = setInterval(function() {
        var t = endtime - Date.now();
        var seconds = (t / 1000);
        seconds = (seconds % 60).toFixed(0);
        timer.innerHTML = "Clock: " + pad(seconds, 2) + " s";
        if (t <= 0) {
            clearInterval(timeinterval);
            timer.innerHTML = "Clock: 0.0 s";
            checkHiscore();
        }
    }, 300);
}

function pad(num, size) {
    var s = "000000000" + num;
    return s.substr(s.length - size);
}

function startHighscoreAttempt() {
    restart();
    initializeTimer('timer', Date.now() + 60 * 1000);
}
<|endoftext|>


<commit_before>if (enyo && enyo.version) {
	enyo.version["enyo-ilib"] = "2.5.3-zzz.1";
}
<commit_msg>Update version string to 2.5.3-zzz.2<commit_after>if (enyo && enyo.version) {
	enyo.version["enyo-ilib"] = "2.5.3-zzz.2";
}
<|endoftext|>


<commit_before>/* jshint node: true */
/* jshint esnext: true */
'use strict';
const fs = require('fs');
const sha = JSON.parse(fs.readFileSync('version.sha','utf8'));

module.exports = sha;
<commit_msg>Exposes only short sha as the version nr<commit_after>/* jshint node: true */
/* jshint esnext: true */
'use strict';
const fs = require('fs');
const version = JSON.parse(fs.readFileSync('version.sha','utf8'));

module.exports = {sha: version.sha.substring(0,7)};
<|endoftext|>


<commit_before>module.exports = '2017-09-05';
<commit_msg>bump version<commit_after>module.exports = '2017-09-12';
<|endoftext|>


<commit_before>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright © 2009 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org

    This file is part of Zotero.

    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.

    ***** END LICENSE BLOCK *****
*/

"use strict";

Zotero.Notifier = new function(){
	var _observers = {};
	var _disabled = false;
	var _types = [
		'collection', 'search', 'share', 'share-items', 'item', 'file',
		'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'publications',
		'bucket', 'relation', 'feed', 'feedItem'
	];
	var _inTransaction;
	var _locked = false;
	var _queue = [];

	this.begin = begin;
	this.reset = reset;
	this.disable = disable;
	this.enable = enable;
	this.isEnabled = isEnabled;

	this.registerObserver = function (ref, types, id, priority) {
		if (types){
			types = Zotero.flattenArguments(types);

			for (var i=0; i<types.length; i++){
				if (_types.indexOf(types[i]) == -1){
					throw new Error("Invalid type '" + types[i] + "'");
				}
			}
		}

		var len = 2;
		var tries = 10;
		do {
			// Increase the hash length if we can't find a unique key
			if (!tries){
				len++;
				tries = 10;
			}

			var hash = (id ? id + '_' : '') + Zotero.randomString(len);
			tries--;
		}
		while (_observers[hash]);

		var msg = "Registering notifier observer '" + hash + "' for "
			+ (types ? '[' + types.join() + ']' : 'all types');
		if (priority) {
			msg += " with priority " + priority;
		}
		Zotero.debug(msg);
		_observers[hash] = {
			ref: ref,
			types: types,
			priority: priority || false
		};
		return hash;
	}

	this.unregisterObserver = function (id) {
		Zotero.debug("Unregistering notifier observer in notifier with id '" + id + "'", 4);
		delete _observers[id];
	}

	/**
	* Trigger a notification to the appropriate observers
	*
	* Possible values:
	*
	* 	event: 'add', 'modify', 'delete', 'move' ('c', for changing parent),
	*		'remove' (ci, it), 'refresh', 'redraw', 'trash', 'unreadCountUpdated'
	* 	type - 'collection', 'search', 'item', 'collection-item', 'item-tag', 'tag',
	*		'group', 'relation', 'feed', 'feedItem'
	* 	ids - single id or array of ids
	*
	* Notes:
	*
	* - If event queuing is on, events will not fire until commit() is called
	* unless _force_ is true.
	*
	* - New events and types should be added to the order arrays in commit()
	**/
	this.trigger = Zotero.Promise.coroutine(function* (event, type, ids, extraData, force) {
		if (_inTransaction && !force) {
			return this.queue(event, type, ids, extraData);
		}

		if (_disabled){
			Zotero.debug("Notifications are disabled");
			return false;
		}

		if (_types && _types.indexOf(type) == -1) {
			throw new Error("Invalid type '" + type + "'");
		}

		ids = Zotero.flattenArguments(ids);

		if (Zotero.Debug.enabled) {
			_logTrigger(event, type, ids, extraData);
		}

		var order = _getObserverOrder(type);
		for (let id of order) {
			//Zotero.debug("Calling notify() with " + event + "/" + type
			//	+ " on observer with id '" + id + "'", 5);

			if (!_observers[id]) {
				Zotero.debug("Observer no longer exists");
				continue;
			}

			// Catch exceptions so all observers get notified even if
			// one throws an error
			try {
				let t = new Date;
				yield Zotero.Promise.resolve(_observers[id].ref.notify(event, type, ids, extraData));
				t = new Date - t;
				if (t > 5) {
					//Zotero.debug(id + " observer finished in " + t + " ms", 5);
				}
			}
			catch (e) {
				Zotero.logError(e);
			}
		}

		return true;
	});

	/**
	 * Queue an event until the end of the current notifier transaction
	 *
	 * Takes the same parameters as trigger()
	 *
	 * @throws If a notifier transaction isn't currently open
	 */
	this.queue = function (event, type, ids, extraData) {
		if (_disabled){
			Zotero.debug("Notifications are disabled");
			return false;
		}

		if (_types && _types.indexOf(type) == -1) {
			throw new Error("Invalid type '" + type + "'");
		}

		ids = Zotero.flattenArguments(ids);

		if (Zotero.Debug.enabled) {
			_logTrigger(event, type, ids, extraData, true);
		}

		if (!_inTransaction) {
			throw new Error("Can't queue event outside of a transaction");
		}

		// Merge with existing queue
		if (!_queue[type]) {
			_queue[type] = [];
		}
		if (!_queue[type][event]) {
			_queue[type][event] = {};
		}
		if (!_queue[type][event].ids) {
			_queue[type][event].ids = [];
			_queue[type][event].data = {};
		}

		// Merge ids
		_queue[type][event].ids = _queue[type][event].ids.concat(ids);

		// Merge extraData keys
		if (extraData) {
			// If just a single id, extra data can be keyed by id or passed directly
			if (ids.length == 1) {
				let id = ids[0];
				_queue[type][event].data[id] = extraData[id] ? extraData[id] : extraData;
			}
			// For multiple ids, check for data keyed by the id
			else {
				for (let i = 0; i < ids.length; i++) {
					let id = ids[i];
					if (extraData[id]) {
						_queue[type][event].data[id] = extraData[id];
					}
				}
			}
		}

		return true;
	}

	function _logTrigger(event, type, ids, extraData, queueing) {
		Zotero.debug("Notifier.trigger("
			+ "'" + event + "', "
			+ "'" + type + "', "
			+ "[" + ids.join() + "]"
			+ (extraData ? ", " + JSON.stringify(extraData) : "")
			+ ")"
			+ (queueing
				? " queued "
				: " called "
			+ "[observers: " + _countObserversForType(type) + "]")
		);
	}

	/**
	 * Get order of observer by priority, with lower numbers having higher priority.
	 * If an observer doesn't have a priority, sort it last.
	 */
	function _getObserverOrder(type) {
		var order = [];
		for (let i in _observers) {
			// Skip observers that don't handle notifications for this type (or all types)
			if (_observers[i].types && _observers[i].types.indexOf(type) == -1) {
				continue;
			}
			order.push({
				id: i,
				priority: _observers[i].priority || false
			});
		}
		order.sort((a, b) => {
			if (a.priority === false && b.priority === false) return 0;
			if (a.priority === false) return 1;
			if (b.priority === false) return -1;
			return a.priority - b.priority;
		});
		return order.map(o => o.id);
	}

	function _countObserversForType(type) {
		var num = 0;
		for (let i in _observers) {
			// Skip observers that don't handle notifications for this type (or all types)
			if (_observers[i].types && _observers[i].types.indexOf(type) == -1) {
				continue;
			}
			num++;
		}
		return num;
	}

	this.untrigger = function (event, type, ids) {
		if (!_inTransaction) {
			throw ("Zotero.Notifier.untrigger() called with no active event queue")
		}

		ids = Zotero.flattenArguments(ids);

		for each(var id in ids) {
			var index = _queue[type][event].ids.indexOf(id);
			if (index == -1) {
				Zotero.debug(event + '-' + type + ' id ' + id +
					' not found in queue in Zotero.Notifier.untrigger()');
				continue;
			}
			_queue[type][event].ids.splice(index, 1);
			delete _queue[type][event].data[id];
		}
	}

	/*
	 * Begin queueing event notifications (i.e. don't notify the observers)
	 *
	 * _lock_ will prevent subsequent commits from running the queue until commit() is called
	 * with the _unlock_ being true
	 *
	 * Note: Be sure the matching commit() gets called (e.g. in a finally{...} block) or
	 * notifications will break until Firefox is restarted or commit(true)/reset() is called manually
	 */
	function begin(lock) {
		if (lock && !_locked) {
			_locked = true;
			var unlock = true;
		}
		else {
			var unlock = false;
		}

		if (_inTransaction) {
			//Zotero.debug("Notifier queue already open", 4);
		}
		else {
			//Zotero.debug("Beginning notifier event queue");
			_inTransaction = true;
		}

		return unlock;
	}

	/*
	 * Send notifications for ids in the event queue
	 *
	 * If the queue is locked, notifications will only run if _unlock_ is true
	 */
	this.commit = Zotero.Promise.coroutine(function* (unlock) {
		// If there's a lock on the event queue and _unlock_ isn't given, don't commit
		if ((unlock == undefined && _locked) || (unlock != undefined && !unlock)) {
			//Zotero.debug("Keeping Notifier event queue open", 4);
			return;
		}

		var runQueue = [];

		function sorter(a, b) {
			return order.indexOf(b) - order.indexOf(a);
		}
		var order = ['collection', 'search', 'item', 'collection-item', 'item-tag', 'tag'];
		_queue.sort();

		var order = ['add', 'modify', 'remove', 'move', 'delete', 'trash'];
		var totals = '';
		for (var type in _queue) {
			if (!runQueue[type]) {
				runQueue[type] = [];
			}

			_queue[type].sort();

			for (var event in _queue[type]) {
				runQueue[type][event] = {
					ids: [],
					data: _queue[type][event].data
				};

				// Remove redundant ids
				for (var i=0; i<_queue[type][event].ids.length; i++) {
					var id = _queue[type][event].ids[i];

					// Don't send modify on nonexistent items or tags
					if (event == 'modify') {
						if (type == 'item' && !(yield Zotero.Items.getAsync(id))) {
							continue;
						}
						else if (type == 'tag' && !(yield Zotero.Tags.getAsync(id))) {
							continue;
						}
					}

					if (runQueue[type][event].ids.indexOf(id) == -1) {
						runQueue[type][event].ids.push(id);
					}
				}

				if (runQueue[type][event].ids.length || event == 'refresh') {
					totals += ' [' + event + '-' + type + ': ' + runQueue[type][event].ids.length + ']';
				}
			}
		}

		reset();

		if (totals) {
			Zotero.debug("Committing notifier event queue" + totals);

			for (var type in runQueue) {
				for (var event in runQueue[type]) {
					if (runQueue[type][event].ids.length || event == 'refresh') {
						yield this.trigger(
							event,
							type,
							runQueue[type][event].ids,
							runQueue[type][event].data,
							true
						);
					}
				}
			}
		}
	});

	/*
	 * Reset the event queue
	 */
	function reset() {
		//Zotero.debug("Resetting notifier event queue");
		_locked = false;
		_queue = [];
		_inTransaction = false;
	}

	//
	// These should rarely be used now that we have event queuing
	//

	/*
	 * Disables Notifier notifications
	 *
	 * Returns false if the Notifier was already disabled, true otherwise
	 */
	function disable() {
		if (_disabled) {
			Zotero.debug('Notifier notifications are already disabled');
			return false;
		}
		Zotero.debug('Disabling Notifier notifications');
		_disabled = true;
		return true;
	}

	function enable() {
		Zotero.debug('Enabling Notifier notifications');
		_disabled = false;
	}

	function isEnabled() {
		return !_disabled;
	}
}
<commit_msg>Fix ordering of notifier events<commit_after>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright © 2009 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org

    This file is part of Zotero.

    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.

    ***** END LICENSE BLOCK *****
*/

"use strict";

Zotero.Notifier = new function(){
	var _observers = {};
	var _disabled = false;
	var _types = [
		'collection', 'search', 'share', 'share-items', 'item', 'file',
		'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'publications',
		'bucket', 'relation', 'feed', 'feedItem'
	];
	var _inTransaction;
	var _locked = false;
	var _queue = [];

	this.begin = begin;
	this.reset = reset;
	this.disable = disable;
	this.enable = enable;
	this.isEnabled = isEnabled;

	this.registerObserver = function (ref, types, id, priority) {
		if (types){
			types = Zotero.flattenArguments(types);

			for (var i=0; i<types.length; i++){
				if (_types.indexOf(types[i]) == -1){
					throw new Error("Invalid type '" + types[i] + "'");
				}
			}
		}

		var len = 2;
		var tries = 10;
		do {
			// Increase the hash length if we can't find a unique key
			if (!tries){
				len++;
				tries = 10;
			}

			var hash = (id ? id + '_' : '') + Zotero.randomString(len);
			tries--;
		}
		while (_observers[hash]);

		var msg = "Registering notifier observer '" + hash + "' for "
			+ (types ? '[' + types.join() + ']' : 'all types');
		if (priority) {
			msg += " with priority " + priority;
		}
		Zotero.debug(msg);
		_observers[hash] = {
			ref: ref,
			types: types,
			priority: priority || false
		};
		return hash;
	}

	this.unregisterObserver = function (id) {
		Zotero.debug("Unregistering notifier observer in notifier with id '" + id + "'", 4);
		delete _observers[id];
	}

	/**
	* Trigger a notification to the appropriate observers
	*
	* Possible values:
	*
	* 	event: 'add', 'modify', 'delete', 'move' ('c', for changing parent),
	*		'remove' (ci, it), 'refresh', 'redraw', 'trash', 'unreadCountUpdated'
	* 	type - 'collection', 'search', 'item', 'collection-item', 'item-tag', 'tag',
	*		'group', 'relation', 'feed', 'feedItem'
	* 	ids - single id or array of ids
	*
	* Notes:
	*
	* - If event queuing is on, events will not fire until commit() is called
	* unless _force_ is true.
	*
	* - New events and types should be added to the order arrays in commit()
	**/
	this.trigger = Zotero.Promise.coroutine(function* (event, type, ids, extraData, force) {
		if (_inTransaction && !force) {
			return this.queue(event, type, ids, extraData);
		}

		if (_disabled){
			Zotero.debug("Notifications are disabled");
			return false;
		}

		if (_types && _types.indexOf(type) == -1) {
			throw new Error("Invalid type '" + type + "'");
		}

		ids = Zotero.flattenArguments(ids);

		if (Zotero.Debug.enabled) {
			_logTrigger(event, type, ids, extraData);
		}

		var order = _getObserverOrder(type);
		for (let id of order) {
			//Zotero.debug("Calling notify() with " + event + "/" + type
			//	+ " on observer with id '" + id + "'", 5);

			if (!_observers[id]) {
				Zotero.debug("Observer no longer exists");
				continue;
			}

			// Catch exceptions so all observers get notified even if
			// one throws an error
			try {
				let t = new Date;
				yield Zotero.Promise.resolve(_observers[id].ref.notify(event, type, ids, extraData));
				t = new Date - t;
				if (t > 5) {
					//Zotero.debug(id + " observer finished in " + t + " ms", 5);
				}
			}
			catch (e) {
				Zotero.logError(e);
			}
		}

		return true;
	});

	/**
	 * Queue an event until the end of the current notifier transaction
	 *
	 * Takes the same parameters as trigger()
	 *
	 * @throws If a notifier transaction isn't currently open
	 */
	this.queue = function (event, type, ids, extraData) {
		if (_disabled){
			Zotero.debug("Notifications are disabled");
			return false;
		}

		if (_types && _types.indexOf(type) == -1) {
			throw new Error("Invalid type '" + type + "'");
		}

		ids = Zotero.flattenArguments(ids);

		if (Zotero.Debug.enabled) {
			_logTrigger(event, type, ids, extraData, true);
		}

		if (!_inTransaction) {
			throw new Error("Can't queue event outside of a transaction");
		}

		// Merge with existing queue
		if (!_queue[type]) {
			_queue[type] = [];
		}
		if (!_queue[type][event]) {
			_queue[type][event] = {};
		}
		if (!_queue[type][event].ids) {
			_queue[type][event].ids = [];
			_queue[type][event].data = {};
		}

		// Merge ids
		_queue[type][event].ids = _queue[type][event].ids.concat(ids);

		// Merge extraData keys
		if (extraData) {
			// If just a single id, extra data can be keyed by id or passed directly
			if (ids.length == 1) {
				let id = ids[0];
				_queue[type][event].data[id] = extraData[id] ? extraData[id] : extraData;
			}
			// For multiple ids, check for data keyed by the id
			else {
				for (let i = 0; i < ids.length; i++) {
					let id = ids[i];
					if (extraData[id]) {
						_queue[type][event].data[id] = extraData[id];
					}
				}
			}
		}

		return true;
	}

	function _logTrigger(event, type, ids, extraData, queueing) {
		Zotero.debug("Notifier.trigger("
			+ "'" + event + "', "
			+ "'" + type + "', "
			+ "[" + ids.join() + "]"
			+ (extraData ? ", " + JSON.stringify(extraData) : "")
			+ ")"
			+ (queueing
				? " queued "
				: " called "
			+ "[observers: " + _countObserversForType(type) + "]")
		);
	}

	/**
	 * Get order of observer by priority, with lower numbers having higher priority.
	 * If an observer doesn't have a priority, sort it last.
	 */
	function _getObserverOrder(type) {
		var order = [];
		for (let i in _observers) {
			// Skip observers that don't handle notifications for this type (or all types)
			if (_observers[i].types && _observers[i].types.indexOf(type) == -1) {
				continue;
			}
			order.push({
				id: i,
				priority: _observers[i].priority || false
			});
		}
		order.sort((a, b) => {
			if (a.priority === false && b.priority === false) return 0;
			if (a.priority === false) return 1;
			if (b.priority === false) return -1;
			return a.priority - b.priority;
		});
		return order.map(o => o.id);
	}

	function _countObserversForType(type) {
		var num = 0;
		for (let i in _observers) {
			// Skip observers that don't handle notifications for this type (or all types)
			if (_observers[i].types && _observers[i].types.indexOf(type) == -1) {
				continue;
			}
			num++;
		}
		return num;
	}

	this.untrigger = function (event, type, ids) {
		if (!_inTransaction) {
			throw ("Zotero.Notifier.untrigger() called with no active event queue")
		}

		ids = Zotero.flattenArguments(ids);

		for each(var id in ids) {
			var index = _queue[type][event].ids.indexOf(id);
			if (index == -1) {
				Zotero.debug(event + '-' + type + ' id ' + id +
					' not found in queue in Zotero.Notifier.untrigger()');
				continue;
			}
			_queue[type][event].ids.splice(index, 1);
			delete _queue[type][event].data[id];
		}
	}

	/*
	 * Begin queueing event notifications (i.e. don't notify the observers)
	 *
	 * _lock_ will prevent subsequent commits from running the queue until commit() is called
	 * with the _unlock_ being true
	 *
	 * Note: Be sure the matching commit() gets called (e.g. in a finally{...} block) or
	 * notifications will break until Firefox is restarted or commit(true)/reset() is called manually
	 */
	function begin(lock) {
		if (lock && !_locked) {
			_locked = true;
			var unlock = true;
		}
		else {
			var unlock = false;
		}

		if (_inTransaction) {
			//Zotero.debug("Notifier queue already open", 4);
		}
		else {
			//Zotero.debug("Beginning notifier event queue");
			_inTransaction = true;
		}

		return unlock;
	}

	/*
	 * Send notifications for ids in the event queue
	 *
	 * If the queue is locked, notifications will only run if _unlock_ is true
	 */
	this.commit = Zotero.Promise.coroutine(function* (unlock) {
		// If there's a lock on the event queue and _unlock_ isn't given, don't commit
		if ((unlock == undefined && _locked) || (unlock != undefined && !unlock)) {
			//Zotero.debug("Keeping Notifier event queue open", 4);
			return;
		}

		var runQueue = [];

		// Sort using order from array, unless missing, in which case sort after
		var getSorter = function (orderArray) {
			return function (a, b) {
				var posA = orderArray.indexOf(a);
				var posB = orderArray.indexOf(b);
				if (posA == -1) posA = 100;
				if (posB == -1) posB = 100;
				return posA - posB;
			}
		};

		var typeOrder = ['collection', 'search', 'item', 'collection-item', 'item-tag', 'tag'];
		var eventOrder = ['add', 'modify', 'remove', 'move', 'delete', 'trash'];

		var queueTypes = Object.keys(_queue);
		queueTypes.sort(getSorter(typeOrder));

		var totals = '';
		for (let type of queueTypes) {
			if (!runQueue[type]) {
				runQueue[type] = [];
			}

			let typeEvents = Object.keys(_queue[type]);
			typeEvents.sort(getSorter(eventOrder));

			for (let event of typeEvents) {
				runQueue[type][event] = {
					ids: [],
					data: _queue[type][event].data
				};

				// Remove redundant ids
				for (var i=0; i<_queue[type][event].ids.length; i++) {
					var id = _queue[type][event].ids[i];

					// Don't send modify on nonexistent items or tags
					if (event == 'modify') {
						if (type == 'item' && !(yield Zotero.Items.getAsync(id))) {
							continue;
						}
						else if (type == 'tag' && !(yield Zotero.Tags.getAsync(id))) {
							continue;
						}
					}

					if (runQueue[type][event].ids.indexOf(id) == -1) {
						runQueue[type][event].ids.push(id);
					}
				}

				if (runQueue[type][event].ids.length || event == 'refresh') {
					totals += ' [' + event + '-' + type + ': ' + runQueue[type][event].ids.length + ']';
				}
			}
		}

		reset();

		if (totals) {
			Zotero.debug("Committing notifier event queue" + totals);

			for (var type in runQueue) {
				for (var event in runQueue[type]) {
					if (runQueue[type][event].ids.length || event == 'refresh') {
						yield this.trigger(
							event,
							type,
							runQueue[type][event].ids,
							runQueue[type][event].data,
							true
						);
					}
				}
			}
		}
	});

	/*
	 * Reset the event queue
	 */
	function reset() {
		//Zotero.debug("Resetting notifier event queue");
		_locked = false;
		_queue = [];
		_inTransaction = false;
	}

	//
	// These should rarely be used now that we have event queuing
	//

	/*
	 * Disables Notifier notifications
	 *
	 * Returns false if the Notifier was already disabled, true otherwise
	 */
	function disable() {
		if (_disabled) {
			Zotero.debug('Notifier notifications are already disabled');
			return false;
		}
		Zotero.debug('Disabling Notifier notifications');
		_disabled = true;
		return true;
	}

	function enable() {
		Zotero.debug('Enabling Notifier notifications');
		_disabled = false;
	}

	function isEnabled() {
		return !_disabled;
	}
}
<|endoftext|>


<commit_before>var spawn = require('child_process').spawn,
    http = require('http'),
    urlmod = require('url'),
    fs = require('fs');
var logLevel = 1;
var vidProc,
    player,
    vidProcLog = '',
    files = [],
    currFile = 0,
    playing = false;
var localAddress = '',
    peerAddress = '',
    port = 3000,
    req,
    res;

// INIT ////////////////////////////////////

// standardized exit function
var exitFunction = function (code) {
    console.log('exiting vidcomm');
    if (code) console.log('exited with code '+code);
    if (vidProcLog.length) console.log(vidProcLog);
    // show cursor
    console.log('\033[?12l\033[?25h');
}

var echo = function (msg) {
    if (logLevel > 0) console.log(msg);
}

// HTTP SERVER /////////////////////////////

// find local ip address
var findLocalAddress = function () {
    require('child_process').exec('ifconfig eth0 | grep \'inet addr:\' | cut -d: -f2 | awk \'{ print $1}\'', function (error, stdout, stderr) {
        if (stdout.search(/192\.168\.1\.\d+/) !== -1) {
            localAddress = stdout.trim();
            echo('local ip address: '+localAddress);
            startServer();
        } else {
            echo('couldn\'t find local ip address, play in offline mode');
            echo('-');
            playNextVideo();
        }
    });
}

// start local http server
var startServer = function () {
    http.createServer(function (req_, res_) {
        req = req_;
        res = res_;
        if (req.method === 'GET') {
            req.on('close', function() { echo('connection remotely closed'); });
            req.on('data', function(data) { /* void */ });
            req.on('end', function() { parseRequest(); });
        } else {
            echo('error: no accepted HTTP method');
            respond('no');
        }
    }).listen(port, localAddress);
    echo('local server running at http://'+localAddress+':'+port);
    echo('-');
    // call peer for status
    callPeer('playing');
}

// parse incoming http requests
var parseRequest = function () {
    var url = urlmod.parse(req.url);
    if (url.href) {
        var cmd = url.href.slice(1);
        echo('peer call: '+cmd);
        if (cmd === 'playing') respond(isPlaying());
        else if (cmd === 'ended') playNextVideo();
        else respond('bad command');
    } else {
        echo('bad request: '+url);
        respond('bad request');
    }
}

// respond to peer http requests
var respond = function (data) {
    echo('response to peer: '+ data);
    var headers = {
        'Content-Length': Buffer.byteLength(data),
        'Content-Type': 'text/plain; charset=utf-8',
        'Expires': (new Date(Date.now)).toUTCString()
    }
    res.writeHead(200, headers);
    res.end(data);
}

// PEER QUERIES //////////////////////////

// call peer
var callPeer = function (msg) {
    echo('call peer: '+ msg);
    var url = 'http://'+peerAddress+':'+port+'/'+msg;
    http.get(url, function(res_) {
        res_.on('data', function (data) { parsePeerResponse(data) });
    }).on('error', function(e) {
        if (e.code === 'ECONNREFUSED') {
            echo('peer isn\'t ready, wait for a call')
            echo('|');
        }
    });
}

// parse peer's response to call
var parsePeerResponse = function (data) {
    echo('peer response: '+ data);
    if (data == 'no') {
        // peer is not playing, play local file
        echo('peer isn\'t playing, play');
        playNextVideo();
    } else {
        echo('peer is playing, wait for a call');
        echo('|');
        // wait for peer message, so, do nothing
    }
}

// VIDEO ///////////////////////////////////

// establish video playing status
var isPlaying = function () {
    if (playing) return 'yes';
    else return 'no';
}

// play video files
var playNextVideo = function () {
    // next file to play
    var filename = files[++currFile];
    // if it doesn't exist, reset filename to the first one
    if (!filename) {
        currFile = 0;
        filename = files[0];
    }
    // play only if the file exists
    if (!filename) {
        console.log('vidcomm playback ended');
        exitFunction();
        process.exit(0);
    } else {
        playing = true;
        echo('play video: '+filename);
        echo('..');
        vidProc = (player === 'omxplayer') ? spawn('omxplayer', ['-o', 'local', filename]) : spawn('mplayer', ['-vm', filename]);
        vidProc.stdout.on('data', function (data) { vidProcLog += data; });
        vidProc.stderr.on('data', function (data) { vidProcLog += data; });
        vidProc.on('exit', function (code) {
            playing = false;
            echo(player+' exited with code '+code);
            callPeer('ended');
            echo('|');
        });
    }
}

// PROCESS /////////////////////////////////

// parse process' incoming arguments
var parseArgv = function () {
    var conf;
    files = [];

    // parse arguments
    if (process.argv.length < 3) {
        // no arguments case
        conf = 'vidcomm.conf';
        echo('no input arguments, will play from: '+conf);
    } else {
        process.argv.forEach(function (val, idx, arr) {
            if (val.search(/^\.*[^\.]+\.conf$/) !== -1) {
                // configuration file case
                conf = val;
                // readConf();
            } else if (val.search(/^\.*[^\.]+\.(mp4|m4v|mov)$/) !== -1) {
                // video filename case
                files.push(val);
            } else if (val.search(/192\.168\.1\.\d+/) !== -1) {
                // peer address case
                peerAddress = val;
            }
        });
        if (conf) echo('input conf file(s): '+conf);
        if (files.length) echo('input video file(s): '+JSON.stringify(files));
        if (peerAddress) echo('input peer ip: '+peerAddress);
    }

    // if a configuration file was given
    if (conf) {
        var data = fs.readFileSync(conf, {encoding:'utf-8'});
        if (data.length) {
            data.split('\n').forEach(function (val, idx, arr) {
                if (val.search(/^\.*[^\.]+\.(mp4|m4v|mov)$/) !== -1) {
                    // video filename case
                    files.push(val);
                } else if (val.search(/192\.168\.1\.\d+/) !== -1) {
                    // peer address case
                    peerAddress = val;
                }
            });
        }
        if (files.length) echo('conf video file(s): '+JSON.stringify(files));
        if (peerAddress) echo('conf peer ip: '+peerAddress);
    }

    // if video files were given
    if (files.length) {
        // clear terminal, move cursor to top left and hide it
        // console.log('\033[2J\033\033[H\033[?25l');

        // if a peer address was given
        if (peerAddress.length) {
            findLocalAddress();
        } else {
            echo('-');
            playNextVideo();
        }
    }

    // failsafe
    if (!conf && !files.length) {
        exitFunction();
        process.exit(1);
    }
}

// check whether other vidcomm process is running on the system
var checkForDuplicates = function () {
    require('child_process').exec('ps aux | grep '+player+' | grep -v grep', function (error, stdout, stderr) {
        if (stdout.length) {
            console.log('a video player is already running on this machine');
            console.log(stdout);
            exitFunction();
            process.exit(1);
        } else {
            echo('no video player is running, vidcomm will start');
            parseArgv();
        }
    });
}

// handle ctrl-C gracefully
process.on('SIGINT', function () {
    console.log(' ');
    exitFunction();
    process.exit(1);
});

// START ///////////////////////////////////

// check which player is available on the system
require('child_process').exec('which omxplayer', function (error, stdout, stderr) {
    if (stdout[0] !== '/') {
        require('child_process').exec('which mplayer', function (error, stdout, stderr) {
            if (stdout[0] !== '/') {
                console.log('no video player available');
                exitFunction();
                process.exit(1);
            } else {
                player = 'mplayer';
                echo('available video player: '+player);
                checkForDuplicates();
            }
        });
    } else {
        player = 'omxplayer';
        echo('available video player: '+player);
        checkForDuplicates();
    }
});<commit_msg>currFile is now reset properly<commit_after>var spawn = require('child_process').spawn,
    http = require('http'),
    urlmod = require('url'),
    fs = require('fs');
var logLevel = 1;
var vidProc,
    player,
    vidProcLog = '',
    files = [],
    currFile = 0,
    playing = false;
var localAddress = '',
    peerAddress = '',
    port = 3000,
    req,
    res;

// INIT ////////////////////////////////////

// standardized exit function
var exitFunction = function (code) {
    console.log('exiting vidcomm');
    if (code) console.log('exited with code '+code);
    if (vidProcLog.length) console.log(vidProcLog);
    // show cursor
    console.log('\033[?12l\033[?25h');
}

var echo = function (msg) {
    if (logLevel > 0) console.log(msg);
}

// HTTP SERVER /////////////////////////////

// find local ip address
var findLocalAddress = function () {
    require('child_process').exec('ifconfig eth0 | grep \'inet addr:\' | cut -d: -f2 | awk \'{ print $1}\'', function (error, stdout, stderr) {
        if (stdout.search(/192\.168\.1\.\d+/) !== -1) {
            localAddress = stdout.trim();
            echo('local ip address: '+localAddress);
            startServer();
        } else {
            echo('couldn\'t find local ip address, play in offline mode');
            echo('-');
            playNextVideo();
        }
    });
}

// start local http server
var startServer = function () {
    http.createServer(function (req_, res_) {
        req = req_;
        res = res_;
        if (req.method === 'GET') {
            req.on('close', function() { echo('connection remotely closed'); });
            req.on('data', function(data) { /* void */ });
            req.on('end', function() { parseRequest(); });
        } else {
            echo('error: no accepted HTTP method');
            respond('no');
        }
    }).listen(port, localAddress);
    echo('local server running at http://'+localAddress+':'+port);
    echo('-');
    // call peer for status
    callPeer('playing');
}

// parse incoming http requests
var parseRequest = function () {
    var url = urlmod.parse(req.url);
    if (url.href) {
        var cmd = url.href.slice(1);
        echo('peer call: '+cmd);
        if (cmd === 'playing') respond(isPlaying());
        else if (cmd === 'ended') playNextVideo();
        else respond('bad command');
    } else {
        echo('bad request: '+url);
        respond('bad request');
    }
}

// respond to peer http requests
var respond = function (data) {
    echo('response to peer: '+ data);
    var headers = {
        'Content-Length': Buffer.byteLength(data),
        'Content-Type': 'text/plain; charset=utf-8',
        'Expires': (new Date(Date.now)).toUTCString()
    }
    res.writeHead(200, headers);
    res.end(data);
}

// PEER QUERIES //////////////////////////

// call peer
var callPeer = function (msg) {
    echo('call peer: '+ msg);
    var url = 'http://'+peerAddress+':'+port+'/'+msg;
    http.get(url, function(res_) {
        res_.on('data', function (data) { parsePeerResponse(data) });
    }).on('error', function(e) {
        if (e.code === 'ECONNREFUSED') {
            echo('peer isn\'t ready, wait for a call')
            echo('|');
        }
    });
}

// parse peer's response to call
var parsePeerResponse = function (data) {
    echo('peer response: '+ data);
    if (data == 'no') {
        // peer is not playing, play local file
        echo('peer isn\'t playing, play');
        playNextVideo();
    } else {
        echo('peer is playing, wait for a call');
        echo('|');
        // wait for peer message, so, do nothing
    }
}

// VIDEO ///////////////////////////////////

// establish video playing status
var isPlaying = function () {
    if (playing) return 'yes';
    else return 'no';
}

// play video files
var playNextVideo = function () {
    // next file to play
    var filename = files[currFile];
    // if the next file doesn't exist, reset filename
    if (!filename) {
        currFile = 0;
        filename = files[0];
    }
    // play only if the file really exists
    if (!filename) {
        console.log('vidcomm playback ended');
        exitFunction();
        process.exit(0);
    } else {
        currFile++;
        playing = true;
        echo('play video: '+filename);
        echo('..');
        vidProc = (player === 'omxplayer') ? spawn('omxplayer', ['-o', 'local', filename]) : spawn('mplayer', ['-vm', filename]);
        vidProc.stdout.on('data', function (data) { vidProcLog += data; });
        vidProc.stderr.on('data', function (data) { vidProcLog += data; });
        vidProc.on('exit', function (code) {
            echo(player+' exited with code '+code);
            playing = false;
            callPeer('ended');
            echo('|');
        });
    }
}

// PROCESS /////////////////////////////////

// parse process' incoming arguments
var parseArgv = function () {
    var conf;
    files = [];

    // parse arguments
    if (process.argv.length < 3) {
        // no arguments case
        conf = 'vidcomm.conf';
        echo('no input arguments, will play from: '+conf);
    } else {
        process.argv.forEach(function (val, idx, arr) {
            if (val.search(/^\.*[^\.]+\.conf$/) !== -1) {
                // configuration file case
                conf = val;
                // readConf();
            } else if (val.search(/^\.*[^\.]+\.(mp4|m4v|mov)$/) !== -1) {
                // video filename case
                files.push(val);
            } else if (val.search(/192\.168\.1\.\d+/) !== -1) {
                // peer address case
                peerAddress = val;
            }
        });
        if (conf) echo('input conf file(s): '+conf);
        if (files.length) echo('input video file(s): '+JSON.stringify(files));
        if (peerAddress) echo('input peer ip: '+peerAddress);
    }

    // if a configuration file was given
    if (conf) {
        var data = fs.readFileSync(conf, {encoding:'utf-8'});
        if (data.length) {
            data.split('\n').forEach(function (val, idx, arr) {
                if (val.search(/^\.*[^\.]+\.(mp4|m4v|mov)$/) !== -1) {
                    // video filename case
                    files.push(val);
                } else if (val.search(/192\.168\.1\.\d+/) !== -1) {
                    // peer address case
                    peerAddress = val;
                }
            });
        }
        if (files.length) echo('conf video file(s): '+JSON.stringify(files));
        if (peerAddress) echo('conf peer ip: '+peerAddress);
    }

    // if video files were given
    if (files.length) {
        // clear terminal, move cursor to top left and hide it
        // console.log('\033[2J\033\033[H\033[?25l');

        // if a peer address was given
        if (peerAddress.length) {
            findLocalAddress();
        } else {
            echo('-');
            playNextVideo();
        }
    }

    // failsafe
    if (!conf && !files.length) {
        exitFunction();
        process.exit(1);
    }
}

// check whether other vidcomm process is running on the system
var checkForDuplicates = function () {
    require('child_process').exec('ps aux | grep '+player+' | grep -v grep', function (error, stdout, stderr) {
        if (stdout.length) {
            console.log('a video player is already running on this machine');
            console.log(stdout);
            exitFunction();
            process.exit(1);
        } else {
            echo('no video player is running, vidcomm will start');
            parseArgv();
        }
    });
}

// handle ctrl-C gracefully
process.on('SIGINT', function () {
    console.log(' ');
    exitFunction();
    process.exit(1);
});

// START ///////////////////////////////////

// check which player is available on the system
require('child_process').exec('which omxplayer', function (error, stdout, stderr) {
    if (stdout[0] !== '/') {
        require('child_process').exec('which mplayer', function (error, stdout, stderr) {
            if (stdout[0] !== '/') {
                console.log('no video player available');
                exitFunction();
                process.exit(1);
            } else {
                player = 'mplayer';
                echo('available video player: '+player);
                checkForDuplicates();
            }
        });
    } else {
        player = 'omxplayer';
        echo('available video player: '+player);
        checkForDuplicates();
    }
});<|endoftext|>