fix crash when seeking without song, new slider
authorAndrew Karpow <andy@ndyk.de>
Thu, 16 Jan 2014 17:32:20 +0000 (18:32 +0100)
committerAndrew Karpow <andy@ndyk.de>
Thu, 16 Jan 2014 17:32:20 +0000 (18:32 +0100)
htdocs/css/mpd.css
htdocs/css/slider.css
htdocs/index.html
htdocs/js/bootstrap-notify.js [new file with mode: 0644]
htdocs/js/bootstrap-slider.js
htdocs/js/jquery.simplePagination.js [deleted file]
htdocs/js/mpd.js
src/http_server.c
src/mpd_client.c
src/mpd_client.h

index b1a3a0a..ff4d073 100644 (file)
@@ -7,18 +7,18 @@ body {
   padding: 40px 15px;
 }
 
-.slider.slider-horizontal {
-  height: 15px;
+#volumeslider {
+  width: 150px;
 }
 
-.slider.slider-horizontal .slider-track {
-  height: 10px;
-  margin-top: -6px;
+#volumeslider .progress {
+  margin-bottom: 0;
 }
 
-.progress {
-  margin-top: 0px;
-  margin-bottom: 0px;
+#volume-icon {
+  float: left;
+  margin-right: 10px;
+  margin-top: 2px;
 }
 
 #counter {
@@ -29,5 +29,44 @@ body {
 }
 
 .btn-group-hover {
-    opacity: 0;
+    opacity: 20%;
 }
+
+.btn:active,
+.btn.active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+  color: #428bca;
+  background-color: #fdfdfd;
+  border-color: #adadad;
+}
+
+
+
+#salamisandwich td:nth-child(3), th:nth-child(3) {
+  text-align: right;
+}
+
+tbody {
+  cursor: pointer;
+}
+
+.notifications {
+  position: fixed;
+  z-index: 9999;
+}
+
+/* Positioning */ 
+.notifications.top-right {
+  right: 10px;
+  top: 60px;
+}
+
+/* Notification Element */
+.notifications > div {
+  position: relative;
+  z-index: 9999;
+  margin: 5px 0px;
+}
\ No newline at end of file
index b527aa8..a7f8370 100644 (file)
@@ -12,7 +12,7 @@
   position: relative;
 }
 .slider.slider-horizontal {
-  width: 210px;
+  width: 100%;
   height: 20px;
 }
 .slider.slider-horizontal .slider-track {
index 6ef24fa..b8e338e 100644 (file)
@@ -13,7 +13,6 @@
   <link href="css/bootstrap.css" rel="stylesheet">
 
   <!-- Custom styles for this template -->
-  <link href="css/slider.css" rel="stylesheet">
   <link href="css/mpd.css" rel="stylesheet">
   <link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
 
@@ -40,7 +39,7 @@
         <ul id="nav_links" class="nav navbar-nav">
           <li id="playlist"><a href="#/">Playlist</a></li>
           <li id="browse"><a href="#/browse/">Browse database</a></li>
-          <li><a href="#" data-toggle="modal" data-target="#about">About</a></li>
+          <li><a href="#" data-toggle="modal" data-target="#about" onclick="getVersion();">About</a></li>
         </ul>
 
         <div class="btn-toolbar navbar-btn navbar-right" role="toolbar">
@@ -48,6 +47,9 @@
             <button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_NEXT');">
               <span class="glyphicon glyphicon-backward"></span>
             </button>
+            <button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_STOP');">
+              <span id="stop-icon" class="glyphicon glyphicon-stop"></span>
+            </button>
             <button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_PAUSE');">
               <span id="play-icon" class="glyphicon glyphicon-pause"></span>
             </button>
@@ -58,8 +60,7 @@
           <div class="btn-group">
             <div class="btn btn-toolbar btn-default">
               <span id="volume-icon" class="glyphicon glyphicon-volume-up"></span>
-              &nbsp;&nbsp;
-              <input type="text" class="span2" value="0" data-slider-min="0" data-slider-max="100" data-slider-step="5" id="volumeslider" data-slider-tooltip="hide">
+              <div id="volumeslider"></div>
             </div>
           </div>
         </div>
@@ -71,7 +72,7 @@
     <div class="row">
 
       <div class="col-md-10 col-xs-12">
-        <div id="alert" class="alert hide"></div>
+        <div class="notifications top-right"></div>
         
         <div class="panel panel-primary">
           <!-- Default panel contents -->
             </h4>
             <p id="counter" class="text pull-right">&nbsp;&nbsp;</p>
 
-            <div class="progress progress-striped active">
-              <div id="progressbar" class="progress-bar navbar-left"  role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100">
-              </div>
-            </div>
+            <div id="progressbar"></div>
+
+
           </div><!-- /.panel-body -->
 
           <ol id="breadcrump" class="breadcrumb">
       </div><!-- /.col-md-10 -->
 
       <div class="col-md-2 col-xs-12" >
-        <div data-spy="affix" data-offset-bottom="10">
+        <div class="btn-toolbar">
           <div class="btn-group-vertical btn-block btn-group-lg" data-toggle="buttons">
             <button id="btnrandom" type="button" class="btn btn-default">
               <span class="glyphicon glyphicon-random"></span> Random
             </button>
           </div>
 
-          <button type="button" class="btn btn-block btn-default btn-lg dropdown-toggle" data-toggle="dropdown">
-            <span class="glyphicon glyphicon-wrench"></span> Options <span class="caret"></span>
-          </button>
-          <ul class="dropdown-menu">
-            <li><a href="#/" onclick="updateDB();"><span class="glyphicon glyphicon-refresh"></span> Update Database</a></li>
-            <li><a href="#/" onclick="socket.send('MPD_API_RM_ALL');"><span class="glyphicon glyphicon-trash"></span> Clear queue</a></li>
-          </ul>
+          <div class="btn-group-vertical btn-block btn-group-lg">
+            <button type="button" class="btn btn-default" onclick="updateDB();">
+              <span class="glyphicon glyphicon-refresh"></span> Update DB
+            </button>
+            <button type="button" class="btn btn-default" onclick="socket.send('MPD_API_RM_ALL');">
+              <span class="glyphicon glyphicon-trash"></span> Clear queue
+            </button>
+          </div>
+
         </div>
       </div><!-- /.col-md-2 -->
     </div><!-- /.row -->
         </div>
         <div class="modal-body">
           <h4><span class="glyphicon glyphicon-play-circle"></span> ympd&nbsp;&nbsp;&nbsp;<small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
-          <br/>
-          <span class="glyphicon glyphicon-play-circle"></span> ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated werbserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.
-          <h5><span class="glyphicon glyphicon-play-circle"></span> ympd uses following excellent software:</h5>
+          <p>
+          ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated werbserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.</p>
+          <p class="text-muted">
+          ympd <span id="ympd_version"></span><br/>
+          libmpdclient <span id="mpd_version"></span><br/>
+          </p>
+          <h5>ympd uses following excellent software:</h5>
           <h6><a href="http://libwebsockets.org">libWebSockets</a> <small>LGPL2.1 + static link exception</small></h6>
           <h6><a href="http://www.musicpd.org/libs/libmpdclient/">libMPDClient</a> <small>BSD License</small></h6>
           <br/>
               <strong>Andrew Karpow</strong><br>
               <a href="mailto:andy@ympd.org">andy@ympd.org</a><br/>
               <a href="http://www.ympd.org">www.ympd.org</a><br/>
-              XMPP: <a href="xmpp:andy@jabber.ccc.de?subscribe">andy_@jabber.ccc.de</a>
+              XMPP: <a href="xmpp:andy_@jabber.ccc.de?subscribe">andy_@jabber.ccc.de</a>
             </address>
           </blockquote>
         </div>
   <!-- Placed at the end of the document so the pages load faster -->
   <script src="js/jquery-1.10.2.min.js"></script>
   <script src="js/bootstrap.min.js"></script>
-  <script src="js/bootstrap-slider.js"></script>
+  <script src="js/bootstrap-notify.js"></script>
+  <script src="js/boostrap-slider.js"></script>
   <script src="js/sammy.js"></script>
   <script src="js/mpd.js"></script>
 </body>
diff --git a/htdocs/js/bootstrap-notify.js b/htdocs/js/bootstrap-notify.js
new file mode 100644 (file)
index 0000000..b4ae197
--- /dev/null
@@ -0,0 +1,585 @@
+/** Notify.js - v0.3.1 - 2013/07/05
+ * http://notifyjs.com/
+ * Copyright (c) 2013 Jaime Pillora - MIT
+ */
+(function(window,document,$,undefined) {
+'use strict';
+
+var Notification, addStyle, blankFieldName, coreStyle, createElem, defaults, encode, find, findFields, getAnchorElement, getStyle, globalAnchors, hAligns, incr, inherit, insertCSS, mainPositions, opposites, parsePosition, pluginClassName, pluginName, pluginOptions, positions, realign, stylePrefixes, styles, vAligns,
+  __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+pluginName = 'notify';
+
+pluginClassName = pluginName + 'js';
+
+blankFieldName = pluginName + "!blank";
+
+positions = {
+  t: 'top',
+  m: 'middle',
+  b: 'bottom',
+  l: 'left',
+  c: 'center',
+  r: 'right'
+};
+
+hAligns = ['l', 'c', 'r'];
+
+vAligns = ['t', 'm', 'b'];
+
+mainPositions = ['t', 'b', 'l', 'r'];
+
+opposites = {
+  t: 'b',
+  m: null,
+  b: 't',
+  l: 'r',
+  c: null,
+  r: 'l'
+};
+
+parsePosition = function(str) {
+  var pos;
+  pos = [];
+  $.each(str.split(/\W+/), function(i, word) {
+    var w;
+    w = word.toLowerCase().charAt(0);
+    if (positions[w]) {
+      return pos.push(w);
+    }
+  });
+  return pos;
+};
+
+styles = {};
+
+coreStyle = {
+  name: 'core',
+  html: "<div class=\"" + pluginClassName + "-wrapper\">\n  <div class=\"" + pluginClassName + "-arrow\"></div>\n  <div class=\"" + pluginClassName + "-container\"></div>\n</div>",
+  css: "." + pluginClassName + "-corner {\n  position: fixed;\n  margin: 5px;\n  z-index: 1050;\n}\n\n." + pluginClassName + "-corner ." + pluginClassName + "-wrapper,\n." + pluginClassName + "-corner ." + pluginClassName + "-container {\n  position: relative;\n  display: block;\n  height: inherit;\n  width: inherit;\n  margin: 3px;\n}\n\n." + pluginClassName + "-wrapper {\n  z-index: 1;\n  position: absolute;\n  display: inline-block;\n  height: 0;\n  width: 0;\n}\n\n." + pluginClassName + "-container {\n  display: none;\n  z-index: 1;\n  position: absolute;\n  cursor: pointer;\n}\n\n[data-notify-text],[data-notify-html] {\n  position: relative;\n}\n\n." + pluginClassName + "-arrow {\n  position: absolute;\n  z-index: 2;\n  width: 0;\n  height: 0;\n}"
+};
+
+stylePrefixes = {
+  "border-radius": ["-webkit-", "-moz-"]
+};
+
+getStyle = function(name) {
+  return styles[name];
+};
+
+addStyle = function(name, def) {
+  var cssText, elem, fields, _ref;
+  if (!name) {
+    throw "Missing Style name";
+  }
+  if (!def) {
+    throw "Missing Style definition";
+  }
+  if (!def.html) {
+    throw "Missing Style HTML";
+  }
+  if ((_ref = styles[name]) != null ? _ref.cssElem : void 0) {
+    if (window.console) {
+      console.warn("" + pluginName + ": overwriting style '" + name + "'");
+    }
+    styles[name].cssElem.remove();
+  }
+  def.name = name;
+  styles[name] = def;
+  cssText = "";
+  if (def.classes) {
+    $.each(def.classes, function(className, props) {
+      cssText += "." + pluginClassName + "-" + def.name + "-" + className + " {\n";
+      $.each(props, function(name, val) {
+        if (stylePrefixes[name]) {
+          $.each(stylePrefixes[name], function(i, prefix) {
+            return cssText += "  " + prefix + name + ": " + val + ";\n";
+          });
+        }
+        return cssText += "  " + name + ": " + val + ";\n";
+      });
+      return cssText += "}\n";
+    });
+  }
+  if (def.css) {
+    cssText += "/* styles for " + def.name + " */\n" + def.css;
+  }
+  if (cssText) {
+    def.cssElem = insertCSS(cssText);
+    def.cssElem.attr('id', "notify-" + def.name);
+  }
+  fields = {};
+  elem = $(def.html);
+  findFields('html', elem, fields);
+  findFields('text', elem, fields);
+  return def.fields = fields;
+};
+
+insertCSS = function(cssText) {
+  var elem;
+  elem = createElem("style");
+  elem.attr('type', 'text/css');
+  $("head").append(elem);
+  try {
+    elem.html(cssText);
+  } catch (e) {
+    elem[0].styleSheet.cssText = cssText;
+  }
+  return elem;
+};
+
+findFields = function(type, elem, fields) {
+  var attr;
+  if (type !== 'html') {
+    type = 'text';
+  }
+  attr = "data-notify-" + type;
+  return find(elem, "[" + attr + "]").each(function() {
+    var name;
+    name = $(this).attr(attr);
+    if (!name) {
+      name = blankFieldName;
+    }
+    return fields[name] = type;
+  });
+};
+
+find = function(elem, selector) {
+  if (elem.is(selector)) {
+    return elem;
+  } else {
+    return elem.find(selector);
+  }
+};
+
+pluginOptions = {
+  clickToHide: true,
+  autoHide: true,
+  autoHideDelay: 5000,
+  arrowShow: true,
+  arrowSize: 5,
+  breakNewLines: true,
+  elementPosition: 'bottom',
+  globalPosition: 'top right',
+  style: 'bootstrap',
+  className: 'error',
+  showAnimation: 'slideDown',
+  showDuration: 400,
+  hideAnimation: 'slideUp',
+  hideDuration: 200,
+  gap: 5
+};
+
+inherit = function(a, b) {
+  var F;
+  F = function() {};
+  F.prototype = a;
+  return $.extend(true, new F(), b);
+};
+
+defaults = function(opts) {
+  return $.extend(pluginOptions, opts);
+};
+
+createElem = function(tag) {
+  return $("<" + tag + "></" + tag + ">");
+};
+
+globalAnchors = {};
+
+getAnchorElement = function(element) {
+  var radios;
+  if (element.is('[type=radio]')) {
+    radios = element.parents('form:first').find('[type=radio]').filter(function(i, e) {
+      return $(e).attr('name') === element.attr('name');
+    });
+    element = radios.first();
+  }
+  return element;
+};
+
+incr = function(obj, pos, val) {
+  var opp, temp;
+  if (typeof val === 'string') {
+    val = parseInt(val, 10);
+  } else if (typeof val !== 'number') {
+    return;
+  }
+  if (isNaN(val)) {
+    return;
+  }
+  opp = positions[opposites[pos.charAt(0)]];
+  temp = pos;
+  if (obj[opp] !== undefined) {
+    pos = positions[opp.charAt(0)];
+    val = -val;
+  }
+  if (obj[pos] === undefined) {
+    obj[pos] = val;
+  } else {
+    obj[pos] += val;
+  }
+  return null;
+};
+
+realign = function(alignment, inner, outer) {
+  if (alignment === 'l' || alignment === 't') {
+    return 0;
+  } else if (alignment === 'c' || alignment === 'm') {
+    return outer / 2 - inner / 2;
+  } else if (alignment === 'r' || alignment === 'b') {
+    return outer - inner;
+  }
+  throw "Invalid alignment";
+};
+
+encode = function(text) {
+  encode.e = encode.e || createElem("div");
+  return encode.e.text(text).html();
+};
+
+Notification = (function() {
+
+  function Notification(elem, data, options) {
+    if (typeof options === 'string') {
+      options = {
+        className: options
+      };
+    }
+    this.options = inherit(pluginOptions, $.isPlainObject(options) ? options : {});
+    this.loadHTML();
+    this.wrapper = $(coreStyle.html);
+    this.wrapper.data(pluginClassName, this);
+    this.arrow = this.wrapper.find("." + pluginClassName + "-arrow");
+    this.container = this.wrapper.find("." + pluginClassName + "-container");
+    this.container.append(this.userContainer);
+    if (elem && elem.length) {
+      this.elementType = elem.attr('type');
+      this.originalElement = elem;
+      this.elem = getAnchorElement(elem);
+      this.elem.data(pluginClassName, this);
+      this.elem.before(this.wrapper);
+    }
+    this.container.hide();
+    this.run(data);
+  }
+
+  Notification.prototype.loadHTML = function() {
+    var style;
+    style = this.getStyle();
+    this.userContainer = $(style.html);
+    return this.userFields = style.fields;
+  };
+
+  Notification.prototype.show = function(show, userCallback) {
+    var args, callback, elems, fn, hidden,
+      _this = this;
+    callback = function() {
+      if (!show && !_this.elem) {
+        _this.destroy();
+      }
+      if (userCallback) {
+        return userCallback();
+      }
+    };
+    hidden = this.container.parent().parents(':hidden').length > 0;
+    elems = this.container.add(this.arrow);
+    args = [];
+    if (hidden && show) {
+      fn = 'show';
+    } else if (hidden && !show) {
+      fn = 'hide';
+    } else if (!hidden && show) {
+      fn = this.options.showAnimation;
+      args.push(this.options.showDuration);
+    } else if (!hidden && !show) {
+      fn = this.options.hideAnimation;
+      args.push(this.options.hideDuration);
+    } else {
+      return callback();
+    }
+    args.push(callback);
+    return elems[fn].apply(elems, args);
+  };
+
+  Notification.prototype.setGlobalPosition = function() {
+    var align, anchor, css, key, main, pAlign, pMain, position;
+    position = this.getPosition();
+    pMain = position[0], pAlign = position[1];
+    main = positions[pMain];
+    align = positions[pAlign];
+    key = pMain + "|" + pAlign;
+    anchor = globalAnchors[key];
+    if (!anchor) {
+      anchor = globalAnchors[key] = createElem("div");
+      css = {};
+      css[main] = 0;
+      if (align === 'middle') {
+        css.top = '45%';
+      } else if (align === 'center') {
+        css.left = '45%';
+      } else {
+        css[align] = 0;
+      }
+      anchor.css(css).addClass("" + pluginClassName + "-corner");
+      $("body").append(anchor);
+    }
+    return anchor.prepend(this.wrapper);
+  };
+
+  Notification.prototype.setElementPosition = function() {
+    var arrowColor, arrowCss, arrowSize, color, contH, contW, css, elemH, elemIH, elemIW, elemPos, elemW, gap, mainFull, margin, opp, oppFull, pAlign, pArrow, pMain, pos, posFull, position, wrapPos, _i, _j, _len, _len1, _ref;
+    position = this.getPosition();
+    pMain = position[0], pAlign = position[1], pArrow = position[2];
+    elemPos = this.elem.position();
+    elemH = this.elem.outerHeight();
+    elemW = this.elem.outerWidth();
+    elemIH = this.elem.innerHeight();
+    elemIW = this.elem.innerWidth();
+    wrapPos = this.wrapper.position();
+    contH = this.container.height();
+    contW = this.container.width();
+    mainFull = positions[pMain];
+    opp = opposites[pMain];
+    oppFull = positions[opp];
+    css = {};
+    css[oppFull] = pMain === 'b' ? elemH : pMain === 'r' ? elemW : 0;
+    incr(css, 'top', elemPos.top - wrapPos.top);
+    incr(css, 'left', elemPos.left - wrapPos.left);
+    _ref = ['top', 'left'];
+    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+      pos = _ref[_i];
+      margin = parseInt(this.elem.css("margin-" + pos), 10);
+      if (margin) {
+        incr(css, pos, margin);
+      }
+    }
+    gap = Math.max(0, this.options.gap - (this.options.arrowShow ? arrowSize : 0));
+    incr(css, oppFull, gap);
+    if (!this.options.arrowShow) {
+      this.arrow.hide();
+    } else {
+      arrowSize = this.options.arrowSize;
+      arrowCss = $.extend({}, css);
+      arrowColor = this.userContainer.css("border-color") || this.userContainer.css("background-color") || 'white';
+      for (_j = 0, _len1 = mainPositions.length; _j < _len1; _j++) {
+        pos = mainPositions[_j];
+        posFull = positions[pos];
+        if (pos === opp) {
+          continue;
+        }
+        color = posFull === mainFull ? arrowColor : 'transparent';
+        arrowCss["border-" + posFull] = "" + arrowSize + "px solid " + color;
+      }
+      incr(css, positions[opp], arrowSize);
+      if (__indexOf.call(mainPositions, pAlign) >= 0) {
+        incr(arrowCss, positions[pAlign], arrowSize * 2);
+      }
+    }
+    if (__indexOf.call(vAligns, pMain) >= 0) {
+      incr(css, 'left', realign(pAlign, contW, elemW));
+      if (arrowCss) {
+        incr(arrowCss, 'left', realign(pAlign, arrowSize, elemIW));
+      }
+    } else if (__indexOf.call(hAligns, pMain) >= 0) {
+      incr(css, 'top', realign(pAlign, contH, elemH));
+      if (arrowCss) {
+        incr(arrowCss, 'top', realign(pAlign, arrowSize, elemIH));
+      }
+    }
+    if (this.container.is(":visible")) {
+      css.display = 'block';
+    }
+    this.container.removeAttr('style').css(css);
+    if (arrowCss) {
+      return this.arrow.removeAttr('style').css(arrowCss);
+    }
+  };
+
+  Notification.prototype.getPosition = function() {
+    var pos, text, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
+    text = this.options.position || (this.elem ? this.options.elementPosition : this.options.globalPosition);
+    pos = parsePosition(text);
+    if (pos.length === 0) {
+      pos[0] = 'b';
+    }
+    if (_ref = pos[0], __indexOf.call(mainPositions, _ref) < 0) {
+      throw "Must be one of [" + mainPositions + "]";
+    }
+    if (pos.length === 1 || ((_ref1 = pos[0], __indexOf.call(vAligns, _ref1) >= 0) && (_ref2 = pos[1], __indexOf.call(hAligns, _ref2) < 0)) || ((_ref3 = pos[0], __indexOf.call(hAligns, _ref3) >= 0) && (_ref4 = pos[1], __indexOf.call(vAligns, _ref4) < 0))) {
+      pos[1] = (_ref5 = pos[0], __indexOf.call(hAligns, _ref5) >= 0) ? 'm' : 'l';
+    }
+    if (pos.length === 2) {
+      pos[2] = pos[1];
+    }
+    return pos;
+  };
+
+  Notification.prototype.getStyle = function(name) {
+    var style;
+    if (!name) {
+      name = this.options.style;
+    }
+    if (!name) {
+      name = 'default';
+    }
+    style = styles[name];
+    if (!style) {
+      throw "Missing style: " + name;
+    }
+    return style;
+  };
+
+  Notification.prototype.updateClasses = function() {
+    var classes, style;
+    classes = ['base'];
+    if ($.isArray(this.options.className)) {
+      classes = classes.concat(this.options.className);
+    } else if (this.options.className) {
+      classes.push(this.options.className);
+    }
+    style = this.getStyle();
+    classes = $.map(classes, function(n) {
+      return "" + pluginClassName + "-" + style.name + "-" + n;
+    }).join(' ');
+    return this.userContainer.attr('class', classes);
+  };
+
+  Notification.prototype.run = function(data, options) {
+    var d, datas, name, type, value,
+      _this = this;
+    if ($.isPlainObject(options)) {
+      $.extend(this.options, options);
+    } else if ($.type(options) === 'string') {
+      this.options.color = options;
+    }
+    if (this.container && !data) {
+      this.show(false);
+      return;
+    } else if (!this.container && !data) {
+      return;
+    }
+    datas = {};
+    if ($.isPlainObject(data)) {
+      datas = data;
+    } else {
+      datas[blankFieldName] = data;
+    }
+    for (name in datas) {
+      d = datas[name];
+      type = this.userFields[name];
+      if (!type) {
+        continue;
+      }
+      if (type === 'text') {
+        d = encode(d);
+        if (this.options.breakNewLines) {
+          d = d.replace(/\n/g, '<br/>');
+        }
+      }
+      value = name === blankFieldName ? '' : '=' + name;
+      find(this.userContainer, "[data-notify-" + type + value + "]").html(d);
+    }
+    this.updateClasses();
+    if (this.elem) {
+      this.setElementPosition();
+    } else {
+      this.setGlobalPosition();
+    }
+    this.show(true);
+    if (this.options.autoHide) {
+      clearTimeout(this.autohideTimer);
+      return this.autohideTimer = setTimeout(function() {
+        return _this.show(false);
+      }, this.options.autoHideDelay);
+    }
+  };
+
+  Notification.prototype.destroy = function() {
+    return this.wrapper.remove();
+  };
+
+  return Notification;
+
+})();
+
+$[pluginName] = function(elem, data, options) {
+  if ((elem && elem.nodeName) || elem.jquery) {
+    $(elem)[pluginName](data, options);
+  } else {
+    options = data;
+    data = elem;
+    new Notification(null, data, options);
+  }
+  return elem;
+};
+
+$.fn[pluginName] = function(data, options) {
+  $(this).each(function() {
+    var inst;
+    inst = getAnchorElement($(this)).data(pluginClassName);
+    if (inst) {
+      return inst.run(data, options);
+    } else {
+      return new Notification($(this), data, options);
+    }
+  });
+  return this;
+};
+
+$.extend($[pluginName], {
+  defaults: defaults,
+  addStyle: addStyle,
+  pluginOptions: pluginOptions,
+  getStyle: getStyle,
+  insertCSS: insertCSS
+});
+
+$(function() {
+  insertCSS(coreStyle.css).attr('id', 'core-notify');
+  return $(document).on('click notify-hide', "." + pluginClassName + "-wrapper", function(e) {
+    var inst;
+    inst = $(this).data(pluginClassName);
+    if (inst && (inst.options.clickToHide || e.type === 'notify-hide')) {
+      return inst.show(false);
+    }
+  });
+});
+
+}(window,document,jQuery));
+
+$.notify.addStyle("bootstrap", {
+  html: "<div>\n<span data-notify-text></span>\n</div>",
+  classes: {
+    base: {
+      "font-weight": "bold",
+      "padding": "8px 15px 8px 14px",
+      "text-shadow": "0 1px 0 rgba(255, 255, 255, 0.5)",
+      "background-color": "#fcf8e3",
+      "border": "1px solid #fbeed5",
+      "border-radius": "4px",
+      "white-space": "nowrap",
+      "padding-left": "25px",
+    },
+    error: {
+      "color": "#B94A48",
+      "background-color": "#F2DEDE",
+      "border-color": "#EED3D7",
+    },
+    success: {
+      "color": "#468847",
+      "background-color": "#DFF0D8",
+      "border-color": "#D6E9C6",
+    },
+    info: {
+      "color": "#3A87AD",
+      "background-color": "#D9EDF7",
+      "border-color": "#BCE8F1",
+    },
+    warn: {
+      "color": "#C09853",
+      "background-color": "#FCF8E3",
+      "border-color": "#FBEED5",
+    }
+  }
+});
index 0dcdf1a..1fa4268 100644 (file)
-/* =========================================================
- * bootstrap-slider.js v2.0.0
- * http://www.eyecon.ro/bootstrap-slider
- * =========================================================
- * Copyright 2012 Stefan Petre
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================================================= */
-!function( $ ) {
-
-       var Slider = function(element, options) {
-               this.element = $(element);
-               this.picker = $('<div class="slider">'+
-                                                       '<div class="slider-track">'+
-                                                               '<div class="slider-selection"></div>'+
-                                                               '<div class="slider-handle"></div>'+
-                                                               '<div class="slider-handle"></div>'+
-                                                       '</div>'+
-                                                       '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'+
-                                               '</div>')
-                                                       .insertBefore(this.element)
-                                                       .append(this.element);
-               this.id = this.element.data('slider-id')||options.id;
-               if (this.id) {
-                       this.picker[0].id = this.id;
-               }
-
-               if (typeof Modernizr !== 'undefined' && Modernizr.touch) {
-                       this.touchCapable = true;
-               }
-
-               var tooltip = this.element.data('slider-tooltip')||options.tooltip;
-
-               this.tooltip = this.picker.find('.tooltip');
-               this.tooltipInner = this.tooltip.find('div.tooltip-inner');
-
-               this.orientation = this.element.data('slider-orientation')||options.orientation;
-               switch(this.orientation) {
-                       case 'vertical':
-                               this.picker.addClass('slider-vertical');
-                               this.stylePos = 'top';
-                               this.mousePos = 'pageY';
-                               this.sizePos = 'offsetHeight';
-                               this.tooltip.addClass('right')[0].style.left = '100%';
-                               break;
-                       default:
-                               this.picker
-                                       .addClass('slider-horizontal')
-                                       .css('width', this.element.outerWidth());
-                               this.orientation = 'horizontal';
-                               this.stylePos = 'left';
-                               this.mousePos = 'pageX';
-                               this.sizePos = 'offsetWidth';
-                               this.tooltip.addClass('top')[0].style.top = -this.tooltip.outerHeight() - 14 + 'px';
-                               break;
-               }
-
-               this.min = this.element.data('slider-min')||options.min;
-               this.max = this.element.data('slider-max')||options.max;
-               this.step = this.element.data('slider-step')||options.step;
-               this.value = this.element.data('slider-value')||options.value;
-               if (this.value[1]) {
-                       this.range = true;
-               }
-
-               this.selection = this.element.data('slider-selection')||options.selection;
-               this.selectionEl = this.picker.find('.slider-selection');
-               if (this.selection === 'none') {
-                       this.selectionEl.addClass('hide');
-               }
-               this.selectionElStyle = this.selectionEl[0].style;
-
-
-               this.handle1 = this.picker.find('.slider-handle:first');
-               this.handle1Stype = this.handle1[0].style;
-               this.handle2 = this.picker.find('.slider-handle:last');
-               this.handle2Stype = this.handle2[0].style;
-
-               var handle = this.element.data('slider-handle')||options.handle;
-               switch(handle) {
-                       case 'round':
-                               this.handle1.addClass('round');
-                               this.handle2.addClass('round');
-                               break
-                       case 'triangle':
-                               this.handle1.addClass('triangle');
-                               this.handle2.addClass('triangle');
-                               break
-               }
-
-               if (this.range) {
-                       this.value[0] = Math.max(this.min, Math.min(this.max, this.value[0]));
-                       this.value[1] = Math.max(this.min, Math.min(this.max, this.value[1]));
-               } else {
-                       this.value = [ Math.max(this.min, Math.min(this.max, this.value))];
-                       this.handle2.addClass('hide');
-                       if (this.selection == 'after') {
-                               this.value[1] = this.max;
-                       } else {
-                               this.value[1] = this.min;
-                       }
-               }
-               this.diff = this.max - this.min;
-               this.percentage = [
-                       (this.value[0]-this.min)*100/this.diff,
-                       (this.value[1]-this.min)*100/this.diff,
-                       this.step*100/this.diff
-               ];
-
-               this.offset = this.picker.offset();
-               this.size = this.picker[0][this.sizePos];
-
-               this.formater = options.formater;
-
-               this.layout();
-
-               if (this.touchCapable) {
-                       // Touch: Bind touch events:
-                       this.picker.on({
-                               touchstart: $.proxy(this.mousedown, this)
-                       });
-               } else {
-                       this.picker.on({
-                               mousedown: $.proxy(this.mousedown, this)
-                       });
-               }
-
-               if (tooltip === 'show') {
-                       this.picker.on({
-                               mouseenter: $.proxy(this.showTooltip, this),
-                               mouseleave: $.proxy(this.hideTooltip, this)
-                       });
-               } else {
-                       this.tooltip.addClass('hide');
-               }
-       };
-
-       Slider.prototype = {
-               constructor: Slider,
-
-               over: false,
-               inDrag: false,
-               
-               showTooltip: function(){
-                       this.tooltip.addClass('in');
-                       //var left = Math.round(this.percent*this.width);
-                       //this.tooltip.css('left', left - this.tooltip.outerWidth()/2);
-                       this.over = true;
-               },
-               
-               hideTooltip: function(){
-                       if (this.inDrag === false) {
-                               this.tooltip.removeClass('in');
-                       }
-                       this.over = false;
-               },
-
-               layout: function(){
-                       this.handle1Stype[this.stylePos] = this.percentage[0]+'%';
-                       this.handle2Stype[this.stylePos] = this.percentage[1]+'%';
-                       if (this.orientation == 'vertical') {
-                               this.selectionElStyle.top = Math.min(this.percentage[0], this.percentage[1]) +'%';
-                               this.selectionElStyle.height = Math.abs(this.percentage[0] - this.percentage[1]) +'%';
-                       } else {
-                               this.selectionElStyle.left = Math.min(this.percentage[0], this.percentage[1]) +'%';
-                               this.selectionElStyle.width = Math.abs(this.percentage[0] - this.percentage[1]) +'%';
-                       }
-                       if (this.range) {
-                               this.tooltipInner.text(
-                                       this.formater(this.value[0]) + 
-                                       ' : ' + 
-                                       this.formater(this.value[1])
-                               );
-                               this.tooltip[0].style[this.stylePos] = this.size * (this.percentage[0] + (this.percentage[1] - this.percentage[0])/2)/100 - (this.orientation === 'vertical' ? this.tooltip.outerHeight()/2 : this.tooltip.outerWidth()/2) +'px';
-                       } else {
-                               this.tooltipInner.text(
-                                       this.formater(this.value[0])
-                               );
-                               this.tooltip[0].style[this.stylePos] = this.size * this.percentage[0]/100 - (this.orientation === 'vertical' ? this.tooltip.outerHeight()/2 : this.tooltip.outerWidth()/2) +'px';
-                       }
-               },
-
-               mousedown: function(ev) {
-
-                       // Touch: Get the original event:
-                       if (this.touchCapable && ev.type === 'touchstart') {
-                               ev = ev.originalEvent;
-                       }
-
-                       this.offset = this.picker.offset();
-                       this.size = this.picker[0][this.sizePos];
-
-                       var percentage = this.getPercentage(ev);
-
-                       if (this.range) {
-                               var diff1 = Math.abs(this.percentage[0] - percentage);
-                               var diff2 = Math.abs(this.percentage[1] - percentage);
-                               this.dragged = (diff1 < diff2) ? 0 : 1;
-                       } else {
-                               this.dragged = 0;
-                       }
-
-                       this.percentage[this.dragged] = percentage;
-                       this.layout();
-
-                       if (this.touchCapable) {
-                               // Touch: Bind touch events:
-                               $(document).on({
-                                       touchmove: $.proxy(this.mousemove, this),
-                                       touchend: $.proxy(this.mouseup, this)
-                               });
-                       } else {
-                               $(document).on({
-                                       mousemove: $.proxy(this.mousemove, this),
-                                       mouseup: $.proxy(this.mouseup, this)
-                               });
-                       }
-
-                       this.inDrag = true;
-                       var val = this.calculateValue();
-                       this.element.trigger({
-                                       type: 'slideStart',
-                                       value: val
-                               }).trigger({
-                                       type: 'slide',
-                                       value: val
-                               });
-                       return false;
-               },
-
-               mousemove: function(ev) {
-                       
-                       // Touch: Get the original event:
-                       if (this.touchCapable && ev.type === 'touchmove') {
-                               ev = ev.originalEvent;
-                       }
-
-                       var percentage = this.getPercentage(ev);
-                       if (this.range) {
-                               if (this.dragged === 0 && this.percentage[1] < percentage) {
-                                       this.percentage[0] = this.percentage[1];
-                                       this.dragged = 1;
-                               } else if (this.dragged === 1 && this.percentage[0] > percentage) {
-                                       this.percentage[1] = this.percentage[0];
-                                       this.dragged = 0;
-                               }
-                       }
-                       this.percentage[this.dragged] = percentage;
-                       this.layout();
-                       var val = this.calculateValue();
-                       this.element
-                               .trigger({
-                                       type: 'slide',
-                                       value: val
-                               })
-                               .data('value', val)
-                               .prop('value', val);
-                       return false;
-               },
-
-               mouseup: function(ev) {
-                       if (this.touchCapable) {
-                               // Touch: Bind touch events:
-                               $(document).off({
-                                       touchmove: this.mousemove,
-                                       touchend: this.mouseup
-                               });
-                       } else {
-                               $(document).off({
-                                       mousemove: this.mousemove,
-                                       mouseup: this.mouseup
-                               });
-                       }
-
-                       this.inDrag = false;
-                       if (this.over == false) {
-                               this.hideTooltip();
-                       }
-                       this.element;
-                       var val = this.calculateValue();
-                       this.element
-                               .trigger({
-                                       type: 'slideStop',
-                                       value: val
-                               })
-                               .data('value', val)
-                               .prop('value', val);
-                       return false;
-               },
-
-               calculateValue: function() {
-                       var val;
-                       if (this.range) {
-                               val = [
-                                       (this.min + Math.round((this.diff * this.percentage[0]/100)/this.step)*this.step),
-                                       (this.min + Math.round((this.diff * this.percentage[1]/100)/this.step)*this.step)
-                               ];
-                               this.value = val;
-                       } else {
-                               val = (this.min + Math.round((this.diff * this.percentage[0]/100)/this.step)*this.step);
-                               this.value = [val, this.value[1]];
-                       }
-                       return val;
-               },
-
-               getPercentage: function(ev) {
-                       if (this.touchCapable) {
-                               ev = ev.touches[0];
-                       }
-                       var percentage = (ev[this.mousePos] - this.offset[this.stylePos])*100/this.size;
-                       percentage = Math.round(percentage/this.percentage[2])*this.percentage[2];
-                       return Math.max(0, Math.min(100, percentage));
-               },
-
-               getValue: function() {
-                       if (this.range) {
-                               return this.value;
-                       }
-                       return this.value[0];
-               },
-
-               setValue: function(val) {
-                       this.value = val;
-
-                       if (this.range) {
-                               this.value[0] = Math.max(this.min, Math.min(this.max, this.value[0]));
-                               this.value[1] = Math.max(this.min, Math.min(this.max, this.value[1]));
-                       } else {
-                               this.value = [ Math.max(this.min, Math.min(this.max, this.value))];
-                               this.handle2.addClass('hide');
-                               if (this.selection == 'after') {
-                                       this.value[1] = this.max;
-                               } else {
-                                       this.value[1] = this.min;
-                               }
-                       }
-                       this.diff = this.max - this.min;
-                       this.percentage = [
-                               (this.value[0]-this.min)*100/this.diff,
-                               (this.value[1]-this.min)*100/this.diff,
-                               this.step*100/this.diff
-                       ];
-                       this.layout();
-               }
-       };
-
-       $.fn.slider = function ( option, val ) {
-               return this.each(function () {
-                       var $this = $(this),
-                               data = $this.data('slider'),
-                               options = typeof option === 'object' && option;
-                       if (!data)  {
-                               $this.data('slider', (data = new Slider(this, $.extend({}, $.fn.slider.defaults,options))));
-                       }
-                       if (typeof option == 'string') {
-                               data[option](val);
-                       }
-               })
-       };
-
-       $.fn.slider.defaults = {
-               min: 0,
-               max: 10,
-               step: 1,
-               orientation: 'horizontal',
-               value: 5,
-               selection: 'before',
-               tooltip: 'show',
-               handle: 'round',
-               formater: function(value) {
-                       return value;
-               }
-       };
-
-       $.fn.slider.Constructor = Slider;
-
-}( window.jQuery );
\ No newline at end of file
+(function($){
+
+    function slider(options){
+        if(typeof options === 'number'){
+            options = $.extend(
+                {
+                    origVal:options
+                },
+                defaults,
+                {
+                    val:(( options < 0 ) ? 0 : ( (options > 100 ) ? 100 : options))
+                }
+            );
+        }
+        else if (options === "get"){
+            var vals = [];
+
+            $(this).each(function() {
+                vals.push($(this).data("sliderValue"));
+            });
+            return vals;
+        }
+        else if(typeof options === 'object'){
+            options = $.extend({origVal:options.val,origBarColor:options.barColor},defaults,options);
+        }
+
+        return $(this).each (function() {
+            var self=$(this);
+
+            if(self.hasClass("slider-wrapper-jq")){
+                if(self.data("dragSlider") === "true")
+                    return;
+                if(typeof options.origVal !== "undefined")
+                    self.slider._setValue.call(self,options.val,null,true);
+                if(typeof options.origBarColor !== "undefined")
+                    self.find('.progress-bar').css("background-color",options.barColor);
+                return;
+            }
+
+            self.addClass("slider-wrapper-jq")
+            .append($("<div class='progress' style='position:relative;left:0'/>")
+            .append("<div class='progress-bar' style='position:width: 30%;background-color: "+
+            options.barColor+"; -webkit-transition:none; transition:none;' />")
+            .append("<div class='btn btn-default ' style='position:absolute;height:100%;padding:6px 10px;margin-left:-10px;vertical-align: top'>"));
+
+            self.find('.progress').on('mousedown', function(evt){
+                self.data("dragSlider","true")
+                .data("startPoint",evt.pageX)
+                .data("endPoint",evt.pageX);
+
+                if(!$(evt.target).hasClass("btn")){
+                    self.slider._setWidthFromEvent.call(self,evt.pageX,null,true);
+                }
+                else{
+                    self.data("btnTarget","true");
+                }
+
+                evt.preventDefault();
+                evt.stopPropagation();
+            });
+
+            $(window).on('mouseup', function(evt){
+                if(self.data("dragSlider")==="true"){
+                    if(!(self.data("btnTarget") === "true" && self.data("startPoint") === self.data("endPoint") )){
+                        self.slider._setWidthFromEvent.call(self,evt.pageX);
+                    }
+
+                    self.removeData("dragSlider")
+                    .removeData("btnTarget")
+                    .removeData("startPoint")
+                    .removeData("endPoint");
+                }
+            }).on('mousemove',function(evt){
+                if(self.data("dragSlider")==="true"){
+                    self.slider._setWidthFromEvent.call(self,evt.pageX,null,true);
+                    self.data("endPoint",evt.pageX);
+                    evt.preventDefault();
+                }
+            });
+
+            self.slider._setValue.call(self,options.val);
+        });
+
+    }
+
+    var defaults={
+        val:50,
+        barColor:"#428bca"
+    },
+    _setWidthFromEvent = function(pageX,reqVals,supress) {
+        if(!reqVals){
+           reqVals =  this.slider._getRequiredValues.call(this);
+        }
+        else{
+            reqVals = null;
+        }
+
+        var width = pageX - reqVals.pbar.offset().left,
+            perc = ((100.0*width) / (reqVals.progw));
+
+        return this.slider._setValue.call(this,perc,reqVals,supress);
+    },
+     _setValue = function (val,reqVals,supress) {
+        if(!reqVals){
+           reqVals =  this.slider._getRequiredValues.call(this);
+        }
+
+        val = ((val<0)?0:((val>100)?100:val));
+        var adjVal= ((val*(100-reqVals.pbutp)/100) + (reqVals.pbutp/2));
+
+        this.data("sliderValue",val);
+        reqVals.pbar.css({width:adjVal+"%"});
+        this.find('div.btn').css('left',adjVal+"%");
+
+        if(supress !== true){
+            this.trigger("slider.newValue",{val:Math.round(val)});
+        }
+
+        return val;
+    },
+    _getRequiredValues = function(){
+        var pbar=this.find('.progress-bar'),
+            progw=this.children('.progress').get(0).clientWidth,
+            pbutp=((this.find('div.btn').get(0).clientWidth*100.0)/progw);
+
+        return {
+            pbar:pbar,
+            progw:progw,
+            pbutp:pbutp
+        };
+    };
+
+    $.fn.slider = slider;
+    $.fn.slider.defaults = defaults;
+    $.fn.slider._getRequiredValues = _getRequiredValues ;
+    $.fn.slider._setWidthFromEvent = _setWidthFromEvent;
+    $.fn.slider._setValue = _setValue;
+
+})(jQuery);
diff --git a/htdocs/js/jquery.simplePagination.js b/htdocs/js/jquery.simplePagination.js
deleted file mode 100644 (file)
index e5a4664..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-/**
-* simplePagination.js v1.6
-* A simple jQuery pagination plugin.
-* http://flaviusmatis.github.com/simplePagination.js/
-*
-* Copyright 2012, Flavius Matis
-* Released under the MIT license.
-* http://flaviusmatis.github.com/license.html
-*/
-
-(function($){
-
-       var methods = {
-               init: function(options) {
-                       var o = $.extend({
-                               items: 1,
-                               itemsOnPage: 1,
-                               pages: 0,
-                               displayedPages: 5,
-                               edges: 2,
-                               currentPage: 1,
-                               hrefTextPrefix: '#page-',
-                               hrefTextSuffix: '',
-                               prevText: 'Prev',
-                               nextText: 'Next',
-                               ellipseText: '&hellip;',
-                               cssStyle: 'light-theme',
-                               labelMap: [],
-                               selectOnClick: true,
-                               onPageClick: function(pageNumber, event) {
-                                       // Callback triggered when a page is clicked
-                                       // Page number is given as an optional parameter
-                               },
-                               onInit: function() {
-                                       // Callback triggered immediately after initialization
-                               }
-                       }, options || {});
-
-                       var self = this;
-
-                       o.pages = o.pages ? o.pages : Math.ceil(o.items / o.itemsOnPage) ? Math.ceil(o.items / o.itemsOnPage) : 1;
-                       o.currentPage = o.currentPage - 1;
-                       o.halfDisplayed = o.displayedPages / 2;
-
-                       this.each(function() {
-                               self.addClass(o.cssStyle + ' simple-pagination').data('pagination', o);
-                               methods._draw.call(self);
-                       });
-
-                       o.onInit();
-
-                       return this;
-               },
-
-               selectPage: function(page) {
-                       methods._selectPage.call(this, page - 1);
-                       return this;
-               },
-
-               prevPage: function() {
-                       var o = this.data('pagination');
-                       if (o.currentPage > 0) {
-                               methods._selectPage.call(this, o.currentPage - 1);
-                       }
-                       return this;
-               },
-
-               nextPage: function() {
-                       var o = this.data('pagination');
-                       if (o.currentPage < o.pages - 1) {
-                               methods._selectPage.call(this, o.currentPage + 1);
-                       }
-                       return this;
-               },
-
-               getPagesCount: function() {
-                       return this.data('pagination').pages;
-               },
-
-               getCurrentPage: function () {
-                       return this.data('pagination').currentPage + 1;
-               },
-
-               destroy: function(){
-                       this.empty();
-                       return this;
-               },
-
-               drawPage: function (page) {
-                       var o = this.data('pagination');
-                       o.currentPage = page - 1;
-                       this.data('pagination', o);
-                       methods._draw.call(this);
-                       return this;
-               },
-
-               redraw: function(){
-                       methods._draw.call(this);
-                       return this;
-               },
-
-               disable: function(){
-                       var o = this.data('pagination');
-                       o.disabled = true;
-                       this.data('pagination', o);
-                       methods._draw.call(this);
-                       return this;
-               },
-
-               enable: function(){
-                       var o = this.data('pagination');
-                       o.disabled = false;
-                       this.data('pagination', o);
-                       methods._draw.call(this);
-                       return this;
-               },
-
-               updateItems: function (newItems) {
-                       var o = this.data('pagination');
-                       o.items = newItems;
-                       o.pages = methods._getPages(o);
-                       this.data('pagination', o);
-                       methods._draw.call(this);
-               },
-
-               updateItemsOnPage: function (itemsOnPage) {
-                       var o = this.data('pagination');
-                       o.itemsOnPage = itemsOnPage;
-                       o.pages = methods._getPages(o);
-                       this.data('pagination', o);
-                       methods._selectPage.call(this, 0);
-                       return this;
-               },
-
-               _draw: function() {
-                       var     o = this.data('pagination'),
-                               interval = methods._getInterval(o),
-                               i,
-                               tagName;
-
-                       methods.destroy.call(this);
-                       
-                       tagName = (typeof this.prop === 'function') ? this.prop('tagName') : this.attr('tagName');
-
-                       var $panel = tagName === 'UL' ? this : $('<ul></ul>').appendTo(this);
-
-                       // Generate Prev link
-                       if (o.prevText) {
-                               methods._appendItem.call(this, o.currentPage - 1, {text: o.prevText, classes: 'prev'});
-                       }
-
-                       // Generate start edges
-                       if (interval.start > 0 && o.edges > 0) {
-                               var end = Math.min(o.edges, interval.start);
-                               for (i = 0; i < end; i++) {
-                                       methods._appendItem.call(this, i);
-                               }
-                               if (o.edges < interval.start && (interval.start - o.edges != 1)) {
-                                       $panel.append('<li class="disabled"><span class="ellipse">' + o.ellipseText + '</span></li>');
-                               } else if (interval.start - o.edges == 1) {
-                                       methods._appendItem.call(this, o.edges);
-                               }
-                       }
-
-                       // Generate interval links
-                       for (i = interval.start; i < interval.end; i++) {
-                               methods._appendItem.call(this, i);
-                       }
-
-                       // Generate end edges
-                       if (interval.end < o.pages && o.edges > 0) {
-                               if (o.pages - o.edges > interval.end && (o.pages - o.edges - interval.end != 1)) {
-                                       $panel.append('<li class="disabled"><span class="ellipse">' + o.ellipseText + '</span></li>');
-                               } else if (o.pages - o.edges - interval.end == 1) {
-                                       methods._appendItem.call(this, interval.end++);
-                               }
-                               var begin = Math.max(o.pages - o.edges, interval.end);
-                               for (i = begin; i < o.pages; i++) {
-                                       methods._appendItem.call(this, i);
-                               }
-                       }
-
-                       // Generate Next link
-                       if (o.nextText) {
-                               methods._appendItem.call(this, o.currentPage + 1, {text: o.nextText, classes: 'next'});
-                       }
-               },
-
-               _getPages: function(o) {
-                       var pages = Math.ceil(o.items / o.itemsOnPage);
-                       return pages || 1;
-               },
-
-               _getInterval: function(o) {
-                       return {
-                               start: Math.ceil(o.currentPage > o.halfDisplayed ? Math.max(Math.min(o.currentPage - o.halfDisplayed, (o.pages - o.displayedPages)), 0) : 0),
-                               end: Math.ceil(o.currentPage > o.halfDisplayed ? Math.min(o.currentPage + o.halfDisplayed, o.pages) : Math.min(o.displayedPages, o.pages))
-                       };
-               },
-
-               _appendItem: function(pageIndex, opts) {
-                       var self = this, options, $link, o = self.data('pagination'), $linkWrapper = $('<li></li>'), $ul = self.find('ul');
-
-                       pageIndex = pageIndex < 0 ? 0 : (pageIndex < o.pages ? pageIndex : o.pages - 1);
-
-                       options = {
-                               text: pageIndex + 1,
-                               classes: ''
-                       };
-
-                       if (o.labelMap.length && o.labelMap[pageIndex]) {
-                               options.text = o.labelMap[pageIndex];
-                       }
-
-                       options = $.extend(options, opts || {});
-
-                       if (pageIndex == o.currentPage || o.disabled) {
-                               if (o.disabled) {
-                                       $linkWrapper.addClass('disabled');
-                               } else {
-                                       $linkWrapper.addClass('active');
-                               }
-                               $link = $('<span class="current">' + (options.text) + '</span>');
-                       } else {
-                               $link = $('<a href="' + o.hrefTextPrefix + (pageIndex + 1) + o.hrefTextSuffix + '" class="page-link">' + (options.text) + '</a>');
-                               $link.click(function(event){
-                                       return methods._selectPage.call(self, pageIndex, event);
-                               });
-                       }
-
-                       if (options.classes) {
-                               $link.addClass(options.classes);
-                       }
-
-                       $linkWrapper.append($link);
-
-                       if ($ul.length) {
-                               $ul.append($linkWrapper);
-                       } else {
-                               self.append($linkWrapper);
-                       }
-               },
-
-               _selectPage: function(pageIndex, event) {
-                       var o = this.data('pagination');
-                       o.currentPage = pageIndex;
-                       if (o.selectOnClick) {
-                               methods._draw.call(this);
-                       }
-                       return o.onPageClick(pageIndex + 1, event);
-               }
-
-       };
-
-       $.fn.pagination = function(method) {
-
-               // Method calling logic
-               if (methods[method] && method.charAt(0) != '_') {
-                       return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
-               } else if (typeof method === 'object' || !method) {
-                       return methods.init.apply(this, arguments);
-               } else {
-                       $.error('Method ' +  method + ' does not exist on jQuery.pagination');
-               }
-
-       };
-
-})(jQuery);
index f343033..7dd87b3 100644 (file)
 var socket;
 var last_state;
 var current_app;
-var is_firefox;
-
-$('#volumeslider').slider().on('slide', function(event) {
-       socket.send("MPD_API_SET_VOLUME,"+event.value);
-});
+var current_song = new Object();
 
 var app = $.sammy(function() {
-       this.before('/', function(e, data) {
-               socket.send("MPD_API_GET_TRACK_INFO");
-               $('#nav_links > li').removeClass('active');
-       });
-
-       this.get('#/', function() {
-               current_app = 'playlist';
-               $('#breadcrump').addClass('hide');
-               $('#salamisandwich').find("tr:gt(0)").remove();
-               //if(is_firefox)
-                       $.get( "/api/get_playlist", socket.onmessage);
-               //else
-               //      socket.send("MPD_API_GET_PLAYLIST");
-
-               $('#panel-heading').text("Playlist");
-               $('#playlist').addClass('active');
-       });
-
-       this.get(/\#\/browse\/(.*)/, function() {
-               current_app = 'browse';
-               $('#breadcrump').removeClass('hide').empty();
-               $('#salamisandwich').find("tr:gt(0)").remove();
-               var path = this.params['splat'];
-               if(path == '')
-                       path = "/";
-
-               //if(is_firefox)
-                       $.get( "/api/get_browse/" + encodeURIComponent(path), socket.onmessage);
-               //else
-               //      socket.send("MPD_API_GET_BROWSE,"+path);
-               
-               $('#panel-heading').text("Browse database: "+path+"");
-               var path_array = path[0].split('/');
-               var full_path = "";
-               $.each(path_array, function(index, chunk) {
-                       if(path_array.length - 1 == index) {
-                               $('#breadcrump').append("<li class=\"active\">"+ chunk + "</li>");
-                               return;
-                       }
-
-                       full_path = full_path + chunk;
-                       $('#breadcrump').append("<li><a href=\"#/browse/" + full_path + "\">"+chunk+"</a></li>");
-                       full_path += "/";
-               });
-               $('#browse').addClass('active');
-       });
-
-       this.get("/", function(context) {
-               context.redirect("#/");
-       });
+    this.before('/', function(e, data) {
+        socket.send("MPD_API_GET_TRACK_INFO");
+        $('#nav_links > li').removeClass('active');
+    });
+
+    this.get('#/', function() {
+        current_app = 'playlist';
+        $('#breadcrump').addClass('hide');
+        $('#salamisandwich').find("tr:gt(0)").remove();
+        $.get( "/api/get_playlist", socket.onmessage);
+
+        $('#panel-heading').text("Playlist");
+        $('#playlist').addClass('active');
+    });
+
+    this.get(/\#\/browse\/(.*)/, function() {
+        current_app = 'browse';
+        $('#breadcrump').removeClass('hide').empty().append("<li><a href=\"#/browse/\">root</a></li>");
+        $('#salamisandwich').find("tr:gt(0)").remove();
+        var path = this.params['splat'][0];
+
+        $.get( "/api/get_browse/" + encodeURIComponent(path), socket.onmessage);
+
+        $('#panel-heading').text("Browse database: "+path+"");
+        var path_array = path.split('/');
+        var full_path = "";
+        $.each(path_array, function(index, chunk) {
+            if(path_array.length - 1 == index) {
+                $('#breadcrump').append("<li class=\"active\">"+ chunk + "</li>");
+                return;
+            }
+
+            full_path = full_path + chunk;
+            $('#breadcrump').append("<li><a href=\"#/browse/" + full_path + "\">"+chunk+"</a></li>");
+            full_path += "/";
+        });
+        $('#browse').addClass('active');
+    });
+
+    this.get("/", function(context) {
+        context.redirect("#/");
+    });
 });
 
 $(document).ready(function(){
-       is_firefox = true;
-       webSocketConnect();
+    webSocketConnect();
+    $("#volumeslider").slider(0);
+    $("#volumeslider").on('slider.newValue', function(evt,data){
+        socket.send("MPD_API_SET_VOLUME,"+data.val);
+    });
+    $('#progressbar').slider(0);
+    $("#progressbar").on('slider.newValue', function(evt,data){
+        if(current_song) {
+            var seekVal = Math.ceil(current_song.totalTime*(data.val/100));
+            socket.send("MPD_API_SET_SEEK,"+current_song.currentSongId+","+seekVal);
+        }
+    });
 });
 
 
 function webSocketConnect() {
-       if (typeof MozWebSocket != "undefined") {
-               socket = new MozWebSocket(get_appropriate_ws_url(), "ympd-client");
-       } else {
-               socket = new WebSocket(get_appropriate_ws_url(), "ympd-client");
-       }
-
-       try {
-               socket.onopen = function() {
-                       console.log("Connected");
-                       app.run();
-
-                       /* Push Initial state on first visit */
-                       //$(window).trigger("statechange")
-               }
-
-               socket.onmessage =function got_packet(msg) {
-                       console.log(typeof msg);
-                       if(msg instanceof MessageEvent) {
-                               if(msg.data === last_state)
-                                       return;
-
-                               var obj = JSON.parse(msg.data);
-                       } else {
-                               var obj = msg;
-                       }
-
-                       switch (obj.type) {
-                               case "playlist":
-                                       if(current_app !== 'playlist')
-                                               break;
-
-                                       $('#salamisandwich > tbody').empty();
-                                       for (var song in obj.data) {
-                                               var minutes = Math.floor(obj.data[song].duration / 60);
-                                               var seconds = obj.data[song].duration - minutes * 60;
-
-                                               $('#salamisandwich > tbody').append(
-                                                       "<tr trackid=\"" + obj.data[song].id + "\"><td>" + obj.data[song].pos + "</td>" +
-                                                       "<td>"+ obj.data[song].title +"</td>" + 
-                                                       "<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +
-                                                       "</td></tr>");
-                                       }
-
-                                       $('#salamisandwich > tbody > tr').on({
-                                               mouseover: function(){
-                                                       if($(this).children().last().has("a").length == 0)
-                                                               $(this).children().last().append(
-                                                                       "<a class=\"pull-right btn-group-hover\" href=\"#/\" " +
-                                                                       "onclick=\"socket.send('MPD_API_RM_TRACK," + $(this).attr("trackid") +"'); $(this).parents('tr').remove();\">" +
-                                                                       "<span class=\"glyphicon glyphicon-trash\"></span></a>")
-                                                               .find('a').fadeTo('fast',1);
-                                               },
-                                               click: function() {
-                                                       $('#salamisandwich > tbody > tr').removeClass('active');
-                                                       socket.send('MPD_API_PLAY_TRACK,'+$(this).attr('trackid'));
-                                                       $(this).addClass('active');
-                                               },
-                                               mouseleave: function(){
-                                                       $(this).children().last().find("a").stop().remove();
-                                               }
-                                       });
-
-                                       break;
-                               case "browse":
-                                       //if(state.data.state !== 'nav_browse')
-                                       //      break;
-                                       if(current_app !== 'browse')
-                                               break;
-
-                                       for (var item in obj.data) {
-                                               switch(obj.data[item].type) {
-                                                       case "directory":
-                                                               $('#salamisandwich > tbody').append(
-                                                                       "<tr uri=\"" + obj.data[item].dir + "\">" +
-                                                                       "<td><span class=\"glyphicon glyphicon-folder-open\"></span></td>" + 
-                                                                       "<td><a href=\"#/browse/"+ obj.data[item].dir +"\">" + basename(obj.data[item].dir) +"</a></td>" + 
-                                                                       "<td></td></tr>");
-                                                               break;
-                                                       case "song":
-                                                               var minutes = Math.floor(obj.data[item].duration / 60);
-                                                               var seconds = obj.data[item].duration - minutes * 60;
-
-                                                               $('#salamisandwich > tbody').append(
-                                                                       "<tr uri=\"" + obj.data[item].uri + "\">" +
-                                                                       "<td><span class=\"glyphicon glyphicon-music\"></span></td>" + 
-                                                                       "<td>" + obj.data[item].title +"</td>" + 
-                                                                       "<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +"</td></tr>");
-                                                               break;
-
-                                                       case "playlist":
-                                                               break;
-                                               }
-                                       }
-
-                                       $('#salamisandwich > tbody > tr').on({
-                                               mouseover: function(){
-                                                       if($(this).children().last().has("a").length == 0)
-                                                               if($(this).attr("uri").length > 0)
-                                                                       $(this).children().last().append(
-                                                                               "<a role=\"button\" class=\"pull-right btn-group-hover\" " +
-                                                                               "onclick=\"socket.send('MPD_API_ADD_TRACK," + $(this).attr("uri") +"'); $(this).parents('tr').addClass('active');\">" +
-                                                                               "<span class=\"glyphicon glyphicon-plus\"></span></a>")
-                                                                               .find('a').fadeTo('fast',1);
-                                               },
-                                               mouseleave: function(){
-                                                       $(this).children().last().find("a").stop().remove();
-                                               }
-                                       });
-
-                                       break;
-                               case "state":
-                                       if(JSON.stringify(obj) === JSON.stringify(last_state))
-                                               break;
-
-                                       var total_minutes = Math.floor(obj.data.totalTime / 60);
-                                       var total_seconds = obj.data.totalTime - total_minutes * 60;
-
-                                       var elapsed_minutes = Math.floor(obj.data.elapsedTime / 60);
-                                       var elapsed_seconds = obj.data.elapsedTime - elapsed_minutes * 60;
-
-                                       $('#volumeslider').slider('setValue', obj.data.volume);
-                                       var progress = Math.floor(100*obj.data.elapsedTime/obj.data.totalTime) + "%";
-                                       $('#progressbar').width(progress);
-
-                                       $('#counter')
-                                               .text(elapsed_minutes + ":" + 
-                                               (elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " +
-                                               total_minutes + ":" + (total_seconds < 10 ? '0' : '') + total_seconds);
-
-                                       $('#salamisandwich > tbody > tr').removeClass('active').css("font-weight", "");
-                                       $('#salamisandwich > tbody > tr[trackid='+obj.data.currentsongid+']').addClass('active').css("font-weight", "bold");
-
-                                       if(obj.data.random)
-                                               $('#btnrandom').addClass("active")
-                                       else
-                                               $('#btnrandom').removeClass("active");
-
-                                       if(obj.data.consume)
-                                               $('#btnconsume').addClass("active")
-                                       else
-                                               $('#btnconsume').removeClass("active");
-
-                                       if(obj.data.single)
-                                               $('#btnsingle').addClass("active")
-                                       else
-                                               $('#btnsingle').removeClass("active");
-
-                                       if(obj.data.repeat)
-                                               $('#btnrepeat').addClass("active")
-                                       else
-                                               $('#btnrepeat').removeClass("active");
-
-                                       if(last_state && (obj.data.state !== last_state.data.state))
-                                               updatePlayIcon(obj.data.state);
-                                       if(last_state && (obj.data.volume !== last_state.data.volume))
-                                               updateVolumeIcon(obj.data.volume);
-
-                                       if(obj.data.elapsedTime <= 1)
-                                               socket.send("MPD_API_GET_TRACK_INFO");
-
-                                       $('#alert').addClass("hide");
-                               last_state = obj;
-                                       break;
-                               case "disconnected":
-                                   $('#alert')
-                                   .text("Server lost connection to MPD Host.")
-                                   .removeClass("hide alert-info")
-                                   .addClass("alert-danger");
+    if (typeof MozWebSocket != "undefined") {
+        socket = new MozWebSocket(get_appropriate_ws_url(), "ympd-client");
+    } else {
+        socket = new WebSocket(get_appropriate_ws_url(), "ympd-client");
+    }
+
+    try {
+        socket.onopen = function() {
+            $('.top-right').notify({
+                message:{text:"Connected"},
+                fadeOut: { enabled: true, delay: 1000 }
+            }).show();
+
+            app.run();
+        }
+
+        socket.onmessage =function got_packet(msg) {
+            if(msg instanceof MessageEvent) {
+                if(msg.data === last_state)
+                    return;
+
+                var obj = JSON.parse(msg.data);
+            } else {
+                var obj = msg;
+            }
+
+            switch (obj.type) {
+                case "playlist":
+                    if(current_app !== 'playlist')
+                        break;
+
+                    $('#salamisandwich > tbody').empty();
+                    for (var song in obj.data) {
+                        var minutes = Math.floor(obj.data[song].duration / 60);
+                        var seconds = obj.data[song].duration - minutes * 60;
+
+                        $('#salamisandwich > tbody').append(
+                            "<tr trackid=\"" + obj.data[song].id + "\"><td>" + obj.data[song].pos + "</td>" +
+                                "<td>"+ obj.data[song].title +"</td>" + 
+                                "<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +
+                        "</td></tr>");
+                    }
+
+                    $('#salamisandwich > tbody > tr').on({
+                        mouseover: function(){
+                            if($(this).children().last().has("a").length == 0)
+                                $(this).children().last().append(
+                                    "<a class=\"pull-right btn-group-hover\" href=\"#/\" " +
+                                        "onclick=\"socket.send('MPD_API_RM_TRACK," + $(this).attr("trackid") +"'); $(this).parents('tr').remove();\">" +
+                                "<span class=\"glyphicon glyphicon-trash\"></span></a>")
+                            .find('a').fadeTo('fast',1);
+                        },
+                        click: function() {
+                            $('#salamisandwich > tbody > tr').removeClass('active');
+                            socket.send('MPD_API_PLAY_TRACK,'+$(this).attr('trackid'));
+                            $(this).addClass('active');
+                        },
+                        mouseleave: function(){
+                            $(this).children().last().find("a").stop().remove();
+                        }
+                    });
+
+                    break;
+                case "browse":
+                    if(current_app !== 'browse')
+                        break;
+
+                    for (var item in obj.data) {
+                        switch(obj.data[item].type) {
+                            case "directory":
+                                $('#salamisandwich > tbody').append(
+                                    "<tr uri=\"" + obj.data[item].dir + "\" class=\"dir\">" +
+                                        "<td><span class=\"glyphicon glyphicon-folder-open\"></span></td>" + 
+                                        "<td><a>" + basename(obj.data[item].dir) + "</a></td>" + 
+                                "<td></td></tr>");
+                            break;
+                        case "song":
+                            var minutes = Math.floor(obj.data[item].duration / 60);
+                            var seconds = obj.data[item].duration - minutes * 60;
+
+                            $('#salamisandwich > tbody').append(
+                                "<tr uri=\"" + obj.data[item].uri + "\" class=\"song\">" +
+                                    "<td><span class=\"glyphicon glyphicon-music\"></span></td>" + 
+                                    "<td>" + obj.data[item].title +"</td>" + 
+                                    "<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +"</td></tr>");
+                                break;
+
+                            case "playlist":
+                                break;
+                        }
+                    }
+
+                    $('#salamisandwich > tbody > tr').on({
+                        mouseenter: function(){
+                            if($(this).is(".dir")) {
+                                    $(this).children().last().append(
+                                        "<a role=\"button\" class=\"pull-right btn-group-hover\">" +
+                                        "Add Directory <span class=\"glyphicon glyphicon-plus\"></span></a>")
+                                .find('a').click(function(e) {
+                                    e.stopPropagation();
+                                    socket.send("MPD_API_ADD_TRACK," + $(this).parents("tr").attr("uri"));
+                                    $('.top-right').notify({
+                                        message:{
+                                            text:"Added " + $('td:nth-child(2)', $(this).parents("tr")).text() + " to playlist "
+                                        }
+                                    }).show();
+                                }).fadeTo('fast',1);
+                            }
+                        },
+                        click: function() {
+                            if($(this).is(".song")) {
+                                socket.send("MPD_API_ADD_TRACK," + $(this).attr("uri"));
+                                $('.top-right').notify({
+                                    message:{
+                                        text:"Added " + $('td:nth-child(2)', this).text() + " to playlist "
+                                    }
+                                }).show();
+
+                            } else
+                                app.setLocation("#/browse/"+$(this).attr("uri"));
+                        },
+                        mouseleave: function(){
+                            $(this).children().last().find("a").stop().remove();
+                        }
+                    });
+
+                    break;
+                case "state":
+                    updatePlayIcon(obj.data.state);
+                    updateVolumeIcon(obj.data.volume);
+
+                    if(JSON.stringify(obj) === JSON.stringify(last_state))
+                        break;
+
+                    current_song.totalTime  = obj.data.totalTime;
+                    current_song.currentSongId = obj.data.currentsongid;
+                    var total_minutes = Math.floor(obj.data.totalTime / 60);
+                    var total_seconds = obj.data.totalTime - total_minutes * 60;
+
+                    var elapsed_minutes = Math.floor(obj.data.elapsedTime / 60);
+                    var elapsed_seconds = obj.data.elapsedTime - elapsed_minutes * 60;
+
+                    $('#volumeslider').slider(obj.data.volume);
+                    var progress = Math.floor(100*obj.data.elapsedTime/obj.data.totalTime);
+                    $('#progressbar').slider(progress);
+
+                    $('#counter')
+                    .text(elapsed_minutes + ":" + 
+                        (elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " +
+                        total_minutes + ":" + (total_seconds < 10 ? '0' : '') + total_seconds);
+
+                    $('#salamisandwich > tbody > tr').removeClass('active').css("font-weight", "");
+                    $('#salamisandwich > tbody > tr[trackid='+obj.data.currentsongid+']').addClass('active').css("font-weight", "bold");
+
+                    if(obj.data.random)
+                        $('#btnrandom').addClass("active")
+                    else
+                        $('#btnrandom').removeClass("active");
+
+                    if(obj.data.consume)
+                        $('#btnconsume').addClass("active")
+                    else
+                        $('#btnconsume').removeClass("active");
+
+                    if(obj.data.single)
+                        $('#btnsingle').addClass("active")
+                    else
+                        $('#btnsingle').removeClass("active");
+
+                    if(obj.data.repeat)
+                        $('#btnrepeat').addClass("active")
+                    else
+                        $('#btnrepeat').removeClass("active");
+
+                    if(obj.data.elapsedTime <= 1)
+                        socket.send("MPD_API_GET_TRACK_INFO");
+
+                    last_state = obj;
+                    break;
+                case "disconnected":
+                    if($('.top-right').has('div').length == 0)
+                        $('.top-right').notify({
+                            message:{text:"ympd lost connection to MPD "},
+                            type: "danger",
+                            fadeOut: { enabled: true, delay: 1000 },
+                        }).show();
+                    break;
+                case "update_playlist":
+                    if(current_app === 'playlist')
+                        $.get( "/api/get_playlist", socket.onmessage);
+                    break;
+                case "current_song":
+                    $('#currenttrack').text(" " + obj.data.title);
+                    if(obj.data.album)
+                        $('#album').text(obj.data.album);
+                    if(obj.data.artist)
+                        $('#artist').text(obj.data.artist);
                     break;
+                case "error":
+                    $('.top-right').notify({
+                        message:{text: obj.data},
+                        type: "danger",
+                    }).show();
+                default:
+                    break;
+            }
+
 
-                               case "current_song":
-                                       $('#currenttrack').text(" " + obj.data.title);
-                                       if(obj.data.album)
-                                               $('#album').text(obj.data.album);
-                                       if(obj.data.artist)
-                                               $('#artist').text(obj.data.artist);
-                               default:
-                                       break;
-                       }
-
-
-               }
-               socket.onclose = function(){
-                       console.log("Disconnected");
-
-                       var seconds = 5;
-                       var tm = setInterval(disconnectAlert,1000);
-
-                       function disconnectAlert() {
-                               $('#alert')
-                               .text("Connection to MPD lost, retrying in " + seconds + " seconds")
-                               .removeClass("hide alert-info")
-                               .addClass("alert-danger");
-
-                               if(seconds-- <= 0) {
-                                       webSocketConnect();
-                                       seconds = 5;
-                                       $('#alert').addClass("hide");
-                                       clearInterval(tm);
-                               }
-                       }
-                       
-               }
-
-       } catch(exception) {
-               alert('<p>Error' + exception);
-       }
+        }
+        socket.onclose = function(){
+            console.log("Disconnected");
+            $('.top-right').notify({
+                message:{text:"Connection to ympd lost, retrying in 3 seconds "},
+                type: "danger", 
+                onClose: function () {
+                    webSocketConnect();
+                }
+            }).show();
+        }
+
+    } catch(exception) {
+        alert('<p>Error' + exception);
+    }
 
 }
 
 function get_appropriate_ws_url()
 {
-       var pcol;
-       var u = document.URL;
-
-       /*
-       /* We open the websocket encrypted if this page came on an
-       /* https:// url itself, otherwise unencrypted
-       /*/
-
-       if (u.substring(0, 5) == "https") {
-               pcol = "wss://";
-               u = u.substr(8);
-       } else {
-               pcol = "ws://";
-               if (u.substring(0, 4) == "http")
-                       u = u.substr(7);
-       }
-
-       u = u.split('/');
-
-       return pcol + u[0];
+    var pcol;
+    var u = document.URL;
+
+    /*
+    /* We open the websocket encrypted if this page came on an
+    /* https:// url itself, otherwise unencrypted
+    /*/
+
+    if (u.substring(0, 5) == "https") {
+        pcol = "wss://";
+        u = u.substr(8);
+    } else {
+        pcol = "ws://";
+        if (u.substring(0, 4) == "http")
+            u = u.substr(7);
+    }
+
+    u = u.split('/');
+
+    return pcol + u[0];
 }
 
 var updateVolumeIcon = function(volume)
 {
-       $("#volume-icon").removeClass("glyphicon-volume-off");
-       $("#volume-icon").removeClass("glyphicon-volume-up");
-       $("#volume-icon").removeClass("glyphicon-volume-down");
-
-       if(volume == 0) {
-               $("#volume-icon").addClass("glyphicon-volume-off");
-       } else if (volume < 50) {
-               $("#volume-icon").addClass("glyphicon-volume-down");
-       } else {
-               $("#volume-icon").addClass("glyphicon-volume-up");
-       }
+    $("#volume-icon").removeClass("glyphicon-volume-off");
+    $("#volume-icon").removeClass("glyphicon-volume-up");
+    $("#volume-icon").removeClass("glyphicon-volume-down");
+
+    if(volume == 0) {
+        $("#volume-icon").addClass("glyphicon-volume-off");
+    } else if (volume < 50) {
+        $("#volume-icon").addClass("glyphicon-volume-down");
+    } else {
+        $("#volume-icon").addClass("glyphicon-volume-up");
+    }
 }
 
 var updatePlayIcon = function(state)
 {
-       $("#play-icon").removeClass("glyphicon-play")
-       .removeClass("glyphicon-pause")
-       .removeClass("glyphicon-stop");
-       $('#track-icon').removeClass("glyphicon-play")
-       .removeClass("glyphicon-pause")
-       .removeClass("glyphicon-stop");
-       
-       if(state == 1) {
-               $("#play-icon").addClass("glyphicon-stop");
-               $('#track-icon').addClass("glyphicon-stop");
-       } else if(state == 2) {
-               $("#play-icon").addClass("glyphicon-pause");
-               $('#track-icon').addClass("glyphicon-play");
-       } else {
-               $("#play-icon").addClass("glyphicon-play");
-               $('#track-icon').addClass("glyphicon-pause");
-       }
+    $("#play-icon").removeClass("glyphicon-play")
+    .removeClass("glyphicon-pause");
+    $('#track-icon').removeClass("glyphicon-play")
+    .removeClass("glyphicon-pause")
+    .removeClass("glyphicon-stop");
+
+    if(state == 1) { // stop
+        $("#play-icon").addClass("glyphicon-play");
+        $('#track-icon').addClass("glyphicon-stop");
+    } else if(state == 2) { // pause
+        $("#play-icon").addClass("glyphicon-pause");
+        $('#track-icon').addClass("glyphicon-play");
+    } else { // play
+        $("#play-icon").addClass("glyphicon-play");
+        $('#track-icon').addClass("glyphicon-pause");
+    }
 }
 
 function updateDB()
 {
-       socket.send('MPD_API_UPDATE_DB');
-
-       $('#alert')
-       .text("Updating MPD Database...")
-       .removeClass("hide alert-danger")
-       .addClass("alert-info");
-
-       setTimeout(function() {
-               $('#alert').addClass("hide");
-       }, 5000);
+    socket.send('MPD_API_UPDATE_DB');
+    $('.top-right').notify({
+        message:{text:"Updating MPD Database... "}
+    }).show();
 }
 
 function basename(path) {
-   return path.split('/').reverse()[0];
+    return path.split('/').reverse()[0];
 }
 
 $('#btnrandom').on('click', function (e) {
-       socket.send("MPD_API_TOGGLE_RANDOM," + ($(this).hasClass('active') ? 0 : 1));
+    socket.send("MPD_API_TOGGLE_RANDOM," + ($(this).hasClass('active') ? 0 : 1));
 
 });
 $('#btnconsume').on('click', function (e) {
-       socket.send("MPD_API_TOGGLE_CONSUME," + ($(this).hasClass('active') ? 0 : 1));
+    socket.send("MPD_API_TOGGLE_CONSUME," + ($(this).hasClass('active') ? 0 : 1));
 
 });
 $('#btnsingle').on('click', function (e) {
-       socket.send("MPD_API_TOGGLE_SINGLE," + ($(this).hasClass('active') ? 0 : 1));
+    socket.send("MPD_API_TOGGLE_SINGLE," + ($(this).hasClass('active') ? 0 : 1));
 });
 $('#btnrepeat').on('click', function (e) {
-       socket.send("MPD_API_TOGGLE_REPEAT," + ($(this).hasClass('active') ? 0 : 1));
+    socket.send("MPD_API_TOGGLE_REPEAT," + ($(this).hasClass('active') ? 0 : 1));
 });
+
+function getVersion()
+{
+    $.get( "/api/get_version", function(response) {
+        $('#ympd_version').text(response.data.ympd_version);
+        $('#mpd_version').text(response.data.mpd_version);
+    });
+}
\ No newline at end of file
index 6b2a71b..511f7c9 100644 (file)
@@ -2,6 +2,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
+#include <errno.h>
 #include <ctype.h>
 #include <mpd/client.h>
 
@@ -10,6 +11,7 @@
 #include "config.h"
 
 char *resource_path = LOCAL_RESOURCE_PATH;
+extern enum mpd_conn_states mpd_conn_state;
 
 struct serveable {
     const char *urlpath;
@@ -25,6 +27,7 @@ static const struct serveable whitelist[] = {
     { "/js/mpd.js", "text/javascript" },
     { "/js/jquery-1.10.2.min.js", "text/javascript" },
     { "/js/bootstrap-slider.js", "text/javascript" },
+    { "/js/bootstrap-notify.js", "text/javascript" },
     { "/js/sammy.js", "text/javascript" },
 
     { "/fonts/glyphicons-halflings-regular.woff", "application/x-font-woff"},
@@ -38,6 +41,11 @@ static const struct serveable whitelist[] = {
     { "/index.html", "text/html" },
 };
 
+static const char http_header[] = "HTTP/1.0 200 OK\x0d\x0a"
+                                  "Server: libwebsockets\x0d\x0a"
+                                  "Content-Type: application/json\x0d\x0a"
+                                  "Content-Length: 000000\x0d\x0a\x0d\x0a";
+
 /* Converts a hex character to its integer value */
 char from_hex(char ch) {
     return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
@@ -70,34 +78,37 @@ int callback_http(struct libwebsocket_context *context,
         void *in, size_t len)
 {
     char *response_buffer, *p;
-    size_t n, response_size;
+    char buf[64];
+    size_t n, response_size = 0;
 
     switch (reason) {
         case LWS_CALLBACK_HTTP:
             if(in && strncmp((const char *)in, "/api/", 5) == 0)
             {
-                response_buffer = (char *)malloc(MAX_SIZE + 100);
-                p = response_buffer;
+
+                p = (char *)malloc(MAX_SIZE + 100);
+                memcpy(p, http_header, sizeof(http_header) - 1);
+                response_buffer = p + sizeof(http_header) - 1;
 
                 /* put content length and payload to buffer */
-                if(strncmp((const char *)in, "/api/get_browse", 15) == 0)
+                if(mpd_conn_state != MPD_CONNECTED) {}
+                else if(strncmp((const char *)in, "/api/get_browse", 15) == 0)
                 {
                     char *url;
-                    if(sscanf(in, "/api/get_browse/%m[^\t\n]", &url))
+                    if(sscanf(in, "/api/get_browse/%m[^\t\n]", &url) == 1)
                     {
                         char *url_decoded = url_decode(url);
-                        printf("searching for %s", url_decoded);
-                        response_size = mpd_put_browse(response_buffer + 98, url_decoded);
+                        response_size = mpd_put_browse(response_buffer, url_decoded);
                         free(url_decoded);
                         free(url);
                     }
                     else
-                        response_size = mpd_put_browse(response_buffer + 98, "/");
+                        response_size = mpd_put_browse(response_buffer, "/");
 
                 }
                 else if(strncmp((const char *)in, "/api/get_playlist", 17)  == 0)
-                    response_size = mpd_put_playlist(response_buffer + 98);
-                else if(strncmp((const char *)in, "/api/version", 17)  == 0)
+                    response_size = mpd_put_playlist(response_buffer);
+                else if(strncmp((const char *)in, "/api/get_version", 16)  == 0)
                     response_size = snprintf(response_buffer, MAX_SIZE,
                             "{\"type\":\"version\",\"data\":{"
                             "\"ympd_version\":\"%d.%d.%d\","
@@ -106,24 +117,15 @@ int callback_http(struct libwebsocket_context *context,
                             YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH,
                             LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION,
                             LIBMPDCLIENT_PATCH_VERSION);
-                else
-                {
-                    /* invalid request, close connection */
-                    free(response_buffer);
-                    return -1;
-                }
-                p += response_size + sprintf(p, "HTTP/1.0 200 OK\x0d\x0a"
-                        "Server: libwebsockets\x0d\x0a"
-                        "Content-Type: application/json\x0d\x0a"
-                        "Content-Length: %6lu\x0d\x0a\x0d\x0a", 
-                        response_size
-                        );
-                response_buffer[98] = '{';
-
-                n = libwebsocket_write(wsi, (unsigned char *)response_buffer,
-                        p - response_buffer, LWS_WRITE_HTTP);
-
-                free(response_buffer);
+
+                /* Copy size to content-length field */
+                sprintf(buf, "%6lu", response_size);
+                memcpy(p + sizeof(http_header) - 11, buf, 6);
+
+                n = libwebsocket_write(wsi, (unsigned char *)p,
+                        sizeof(http_header) - 1 + response_size, LWS_WRITE_HTTP);
+
+                free(p);
                 /*
                  * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
                  */
index b38a9a2..6ceb56a 100644 (file)
@@ -41,12 +41,21 @@ int callback_ympd(struct libwebsocket_context *context,
             }
             p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
 
-            if(mpd_conn_state != MPD_CONNECTED) {
+            if(pss->do_send & DO_SEND_ERROR) {
+                n = snprintf(p, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}", 
+                    mpd_connection_get_error_message(conn));
+                pss->do_send &= ~DO_SEND_ERROR;
+
+                /* Try to recover error */
+                if (!mpd_connection_clear_error(conn))
+                    mpd_conn_state = MPD_FAILURE;
+            }
+            else if(mpd_conn_state != MPD_CONNECTED) {
                 n = snprintf(p, MAX_SIZE, "{\"type\":\"disconnected\"}");
             }
-            //else if((pss->queue_version != queue_version) || (pss->do_send & DO_SEND_PLAYLIST)) {
-            else if(pss->do_send & DO_SEND_PLAYLIST) {
-                n = mpd_put_playlist(p);
+            else if((pss->queue_version != queue_version) || (pss->do_send & DO_SEND_PLAYLIST)) {
+                /*n = mpd_put_playlist(p);*/
+                n = snprintf(p, MAX_SIZE, "{\"type\":\"update_playlist\"}");
                 pss->queue_version = queue_version;
                 pss->do_send &= ~DO_SEND_PLAYLIST;
             }
@@ -78,25 +87,20 @@ int callback_ympd(struct libwebsocket_context *context,
                 pss->do_send |= DO_SEND_PLAYLIST;
             else if(!strcmp((const char *)in, MPD_API_GET_TRACK_INFO))
                 pss->do_send |= DO_SEND_TRACK_INFO;
-            else if(!strcmp((const char *)in, MPD_API_UPDATE_DB)) {
-                mpd_send_update(conn, NULL);
-                mpd_response_finish(conn);
-            }
-            else if(!strcmp((const char *)in, MPD_API_SET_PAUSE)) {
-                mpd_send_toggle_pause(conn);
-                mpd_response_finish(conn);
-            }
-            else if(!strcmp((const char *)in, MPD_API_SET_PREV)) {
-                mpd_send_previous(conn);
-                mpd_response_finish(conn);
-            }
-            else if(!strcmp((const char *)in, MPD_API_SET_NEXT)) {
-                mpd_send_next(conn);
-                mpd_response_finish(conn);
-            }
-            else if(!strcmp((const char *)in, MPD_API_RM_ALL)) {
+            else if(!strcmp((const char *)in, MPD_API_UPDATE_DB))
+                mpd_run_update(conn, NULL);
+            else if(!strcmp((const char *)in, MPD_API_SET_PAUSE))
+                mpd_run_toggle_pause(conn);
+            else if(!strcmp((const char *)in, MPD_API_SET_PREV))
+                mpd_run_previous(conn);
+            else if(!strcmp((const char *)in, MPD_API_SET_NEXT))
+                mpd_run_next(conn);
+            else if(!strcmp((const char *)in, MPD_API_SET_PLAY))
+                mpd_run_play(conn);
+            else if(!strcmp((const char *)in, MPD_API_SET_STOP))
+                mpd_run_stop(conn);
+            else if(!strcmp((const char *)in, MPD_API_RM_ALL))
                 mpd_run_clear(conn);
-            }
             else if(!strncmp((const char *)in, MPD_API_RM_TRACK, sizeof(MPD_API_RM_TRACK)-1)) {
                 unsigned id;
                 if(sscanf(in, "MPD_API_RM_TRACK,%u", &id))
@@ -129,9 +133,15 @@ int callback_ympd(struct libwebsocket_context *context,
             }
             else if(!strncmp((const char *)in, MPD_API_SET_VOLUME, sizeof(MPD_API_SET_VOLUME)-1)) {
                 unsigned int volume;
-                if(sscanf(in, "MPD_API_SET_VOLUME,%ud", &volume) && volume < 100)
+                if(sscanf(in, "MPD_API_SET_VOLUME,%ud", &volume) && volume <= 100)
                     mpd_run_set_volume(conn, volume);
             }
+            else if(!strncmp((const char *)in, MPD_API_SET_SEEK, sizeof(MPD_API_SET_SEEK)-1)) {
+                unsigned int seek, songid;
+                if(sscanf(in, "MPD_API_SET_SEEK,%u,%u", &songid, &seek)) {
+                    mpd_run_seek_id(conn, songid, seek);
+                }
+            }
             else if(!strncmp((const char *)in, MPD_API_GET_BROWSE, sizeof(MPD_API_GET_BROWSE)-1)) {
                 char *dir;
                 if(sscanf(in, "MPD_API_GET_BROWSE,%m[^\t\n]", &dir) && dir != NULL) {
@@ -146,6 +156,10 @@ int callback_ympd(struct libwebsocket_context *context,
                     free(uri);
                 }
             }
+
+            if(mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
+                pss->do_send |= DO_SEND_ERROR;
+
             break;
 
         default:
@@ -283,8 +297,12 @@ int mpd_put_playlist(char *buffer)
 
     if (!mpd_send_list_queue_meta(conn)) {
         lwsl_err("MPD mpd_send_list_queue_meta: %s\n", mpd_connection_get_error_message(conn));
-        mpd_conn_state = MPD_FAILURE;
-        return 0;
+        cur += snprintf(cur, end  - cur, "{\"type\":\"error\",\"data\":\"%s\"}", 
+            mpd_connection_get_error_message(conn));
+
+        if (!mpd_connection_clear_error(conn))
+            mpd_conn_state = MPD_FAILURE;
+        return cur - buffer;
     }
 
     cur += snprintf(cur, end  - cur, "{\"type\": \"playlist\", \"data\": [ ");
@@ -319,8 +337,12 @@ int mpd_put_browse(char *buffer, char *path)
 
     if (!mpd_send_list_meta(conn, path)) {
         lwsl_err("MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(conn));
-        mpd_conn_state = MPD_FAILURE;
-        return 0;
+        cur += snprintf(cur, end  - cur, "{\"type\":\"error\",\"data\":\"%s\"}", 
+            mpd_connection_get_error_message(conn));
+
+        if (!mpd_connection_clear_error(conn))
+            mpd_conn_state = MPD_FAILURE;
+        return cur - buffer;
     }
     cur += snprintf(cur, end  - cur, "{\"type\":\"browse\",\"data\":[ ");
 
index 9320209..287ecc1 100644 (file)
@@ -9,6 +9,8 @@
 #define DO_SEND_PLAYLIST   (1 << 1)
 #define DO_SEND_TRACK_INFO (1 << 2)
 #define DO_SEND_BROWSE     (1 << 3)
+#define DO_SEND_ERROR      (1 << 4)
+
 
 #define MPD_API_GET_SEEK         "MPD_API_GET_SEEK"
 #define MPD_API_GET_PLAYLIST     "MPD_API_GET_PLAYLIST"