浏览代码

First commit

Info @ meCoffee 6 年之前
当前提交
ab0e3aa325
共有 48 个文件被更改,包括 15741 次插入0 次删除
  1. 339 0
      LICENSE.txt
  2. 5 0
      README.md
  3. 11 0
      _locales/en/messages.json
  4. 47 0
      background.js
  5. 183 0
      graph.js
  6. 二进制
      images/logo-114.png
  7. 二进制
      images/logo-128.png
  8. 二进制
      images/logo-144.png
  9. 二进制
      images/logo-16.png
  10. 二进制
      images/logo-32.png
  11. 二进制
      images/logo-48.png
  12. 二进制
      images/logo-512.png
  13. 二进制
      images/logo-57.png
  14. 二进制
      images/logo-64.png
  15. 二进制
      images/logo-72.png
  16. 二进制
      images/logo-96.png
  17. 二进制
      images/tile-1400.png
  18. 二进制
      images/tile-440.png
  19. 二进制
      images/tile-920.png
  20. 502 0
      index.html
  21. 260 0
      lib/bootstrap-select.css
  22. 1215 0
      lib/bootstrap-select.js
  23. 248 0
      lib/bootstrap-slider.css
  24. 29 0
      lib/bootstrap-slider.min.js
  25. 196 0
      lib/bootstrap-switch.css
  26. 710 0
      lib/bootstrap-switch.js
  27. 146 0
      lib/bootstrap-timepicker.css
  28. 1097 0
      lib/bootstrap-timepicker.js
  29. 476 0
      lib/bootstrap/css/bootstrap-theme.css
  30. 1 0
      lib/bootstrap/css/bootstrap-theme.css.map
  31. 5 0
      lib/bootstrap/css/bootstrap-theme.min.css
  32. 6584 0
      lib/bootstrap/css/bootstrap.css
  33. 1 0
      lib/bootstrap/css/bootstrap.css.map
  34. 5 0
      lib/bootstrap/css/bootstrap.min.css
  35. 二进制
      lib/bootstrap/fonts/glyphicons-halflings-regular.eot
  36. 288 0
      lib/bootstrap/fonts/glyphicons-halflings-regular.svg
  37. 二进制
      lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
  38. 二进制
      lib/bootstrap/fonts/glyphicons-halflings-regular.woff
  39. 二进制
      lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
  40. 2317 0
      lib/bootstrap/js/bootstrap.js
  41. 7 0
      lib/bootstrap/js/bootstrap.min.js
  42. 13 0
      lib/bootstrap/js/npm.js
  43. 61 0
      lib/d3-gauge-simple.css
  44. 407 0
      lib/d3-gauge.js
  45. 5 0
      lib/d3.v3.min.js
  46. 4 0
      lib/jquery-1.11.2.min.js
  47. 24 0
      manifest.json
  48. 555 0
      mecoffee.js

+ 339 - 0
LICENSE.txt

@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+Public meBarista for Chrome repo : https://mecoffee.nl/mebarista/
+
+meBarista for Chrome only has access to Bluetooth when it is run as an 'app'.
+
+This codebase is also used for /mebarista-demo and the 'sharer'.

+ 11 - 0
_locales/en/messages.json

@@ -0,0 +1,11 @@
+{
+  "appName": {
+    "message": "meBarista",
+    "description": "The title of the application, displayed in the web store."
+  },
+  "appDesc": {
+    "message": "meBarista for the meCoffee espresso-machine controller. Cheaper, better and more versatile than an Auber PID. Really. Have a nice coffee!.", 
+    "description":"The description of the application, displayed in the web store."
+  }
+}
+

+ 47 - 0
background.js

@@ -0,0 +1,47 @@
+var background_mecoffee_socket_id;
+
+// https://bugs.chromium.org/p/chromium/issues/detail?id=370744
+
+chrome.app.runtime.onLaunched.addListener(function() {
+  
+  chrome.app.window.create('index.html', {
+    'bounds': {
+      'width': 800,
+      'height': 600
+    } },
+
+    function( win ) {
+    	console.log( 'in win callback');
+
+    	win.onClosed.addListener( function() { 
+    		console.log( 'window close'); 
+
+    		chrome.bluetoothSocket.disconnect( background_mecoffee_socket_id );
+  			chrome.bluetoothSocket.close( background_mecoffee_socket_id );
+
+    	} );
+    }    
+
+  );
+
+  chrome.bluetooth.getAdapterState(function(adapter) {
+  console.log("Adapter " + adapter.address + ": " + adapter.name);
+  });
+
+});
+
+chrome.runtime.onMessage.addListener(
+  function(request, sender, sendResponse) {
+  	console.log('msgZ!')
+    //console.log(sender.tab ?
+      //          "from a content script:" + sender.tab.url :
+        //        "from the extension");
+    if (request.type == "socket" ) {
+    	console.log( 'socketid received' );
+
+    	background_mecoffee_socket_id = request.socket;
+
+      // sendResponse({farewell: "goodbye"});
+    }
+  });
+

+ 183 - 0
graph.js

@@ -0,0 +1,183 @@
+
+function meCoffeeGraph () {
+  this.n = 243;
+  this.data = d3.range(this.n).map(function() { return 18; });
+  this.data2 = d3.range(this.n).map(function() { return 18; });
+  this.data_sp = d3.range(this.n).map(function() { return 18; });    
+}
+
+// superclass method
+meCoffeeGraph.prototype.draw = function( ) {
+
+this.duration = 750;
+
+this.now = new Date(Date.now() - this.duration);
+var
+    count = 0;
+
+var margin = {top: 6, right: 0, bottom: 20, left: 30};
+
+var rect = $('#graph').parent()[0].getBoundingClientRect();
+
+this.width = rect.width - margin.right - margin.left;
+this.height = rect.height - margin.top - margin.bottom;
+
+this.x = d3.time.scale()
+    .domain([this.now - (this.n - 2) * this.duration, this.now - this.duration])
+    .range([0, this.width]);
+
+this.y = d3.scale.linear()
+    .range([this.height, 0]);
+
+this.line = d3.svg.line()
+    .interpolate("basis")
+    .y(function(d, i) { return thiss.y(d); })
+    .x(function(d, i) { return thiss.x(thiss.now - (thiss.n - 1 - i) * thiss.duration); });
+    //.x(function(d, i) { return this.y(i); });
+    
+this.line_tmp2 = d3.svg.line()
+    .interpolate("basis")
+    .y(function(d, i) { return thiss.y(d); })
+    .x(function(d, i) { return thiss.x(thiss.now - (thiss.n - 1 - i) * thiss.duration); });
+    //.x(function(d, i) { return this.y(i); });    
+
+this.line2 = d3.svg.line()
+    .interpolate("basis")
+    .x(function(d, i) { return thiss.x(thiss.now - (thiss.n - 1 - i) * thiss.duration); })
+    .y(function(d, i) { return thiss.y(d); });
+
+$('#graph svg').remove(  );
+
+this.svg = d3.select("#graph").append("svg")
+    .attr( "class", "svg-content" )
+    .attr( "viewBox", "0 0 " + rect.width +  " " + rect.height )
+    .attr( "preserveAspectRatio", "none" )
+    .attr("id", "svg-g")
+    //.attr("width", width + margin.left + margin.right)
+    //.attr("height", height + margin.top + margin.bottom)
+    //.style("margin-left", -margin.left + "px")
+  .append("g")
+    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+this.svg.append("defs").append("clipPath")
+    .attr("id", "clip")
+  .append("rect")
+    .attr("width", this.width)
+    .attr("height", this.height);
+
+this.axis = this.svg.append("g")
+    .attr("class", "x axis")
+    .attr("transform", "translate(0," + this.height + ")")
+    .call( this.x.axis = d3.svg.axis().scale(this.x).orient("bottom"));
+
+this.yaxis = this.svg.append("g")
+    .attr("class", "y axis")
+    .call( this.y.axis = d3.svg.axis().scale(this.y).ticks( 5 ).orient("left"));
+
+this.path = this.svg.append("g")
+    .attr("clip-path", "url(#clip)")
+    .style("stroke", "blue")
+  .append("path")
+    .datum(this.data)
+    .attr("id", "line_tmp");
+
+this.path = this.svg.append("g")
+    .attr("clip-path", "url(#clip)")
+    .style("stroke", "red")
+  .append("path")
+    .datum(this.data2)
+    .attr("id", "line_tmp2");
+
+this.path_sp = this.svg.append("g")
+    .attr("clip-path", "url(#clip)")
+    .style("stroke", "green")
+  .append("path")
+    .datum(this.data_sp)
+    .attr("id", "line_sp");
+
+this.transition = d3.select({}).transition()
+    .duration(100)
+    .ease("linear");
+
+}
+
+meCoffeeGraph.prototype.tick = function( tmp, tmp2, sp, timestamp ) {
+  thiss = this
+
+  if( !this.started )
+    this.started = new Date();
+
+  //thiss.transition = thiss.transition.each(function() {
+
+    // update the domains
+    if( timestamp )
+        thiss.now = new Date( this.started.getTime() + timestamp * 1000 );
+    else
+        thiss.now = new Date();
+
+    thiss.x.domain([thiss.now - (thiss.n - 2) * thiss.duration, thiss.now - thiss.duration]);
+    
+
+    var data2_min = d3.min(thiss.data2),
+        data2_max = d3.max(thiss.data2),
+        data_min = d3.min(thiss.data),
+        data_max = d3.max(thiss.data);
+
+    if( data2_min > 5 && data2_max < 190 ) {
+        data_min = Math.min( data2_min, data_min );
+        data_max = Math.max( data2_max, data_max );
+    }
+
+    thiss.y.domain( [ data_min * 0.90, data_max * 1.1 ] );
+
+    // push the accumulated count onto the back, and reset the count
+    thiss.data.push( tmp );
+    thiss.data2.push( tmp2 );
+    thiss.data_sp.push( sp );
+    count = 0;
+
+    // redraw the line
+    //thiss = this
+    thiss.svg.select("#line_tmp")
+        .attr("d", thiss.line )
+        .attr("transform", null); 
+
+    thiss.svg.select("#line_tmp2")
+        .attr("d", thiss.line_tmp2 )
+        .attr("transform", null); 
+
+    thiss.svg.select("#line_sp")
+        .attr("d", thiss.line2 )
+        .attr("transform", null);
+
+    // slide the x-axis left
+    thiss.axis.call( thiss.x.axis);
+    thiss.yaxis.call( thiss.y.axis);
+
+    // slide the line left
+    thiss.path.transition()
+        .attr("transform", "translate(" + thiss.x(thiss.now - (thiss.n - 1) * thiss.duration) + ")");
+
+     thiss.path_sp.transition()
+        .attr("transform", "translate(" + thiss.x(thiss.now - (thiss.n - 1) * thiss.duration) + ")");
+
+    // pop the old data point off the front
+    thiss.data.shift();
+    thiss.data2.shift();
+    thiss.data_sp.shift();
+
+  //}).transition(); //.each("start", tick);
+}
+
+meCoffeeGraph.prototype.redraw = function() {
+    thiss = this;
+
+    thiss.svg.select("#line_tmp")
+        .attr("d", thiss.line )
+        .attr("transform", null); 
+
+    thiss.svg.select("#line_sp")
+        .attr("d", thiss.line2 )
+        .attr("transform", null);
+}
+

二进制
images/logo-114.png


二进制
images/logo-128.png


二进制
images/logo-144.png


二进制
images/logo-16.png


二进制
images/logo-32.png


二进制
images/logo-48.png


二进制
images/logo-512.png


二进制
images/logo-57.png


二进制
images/logo-64.png


二进制
images/logo-72.png


二进制
images/logo-96.png


二进制
images/tile-1400.png


二进制
images/tile-440.png


二进制
images/tile-920.png


+ 502 - 0
index.html

@@ -0,0 +1,502 @@
+<!DOCTYPE html>
+<html>
+  <head>
+  	<meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>meBarista</title>
+    <link rel="shortcut icon" href="https://mecoffee.nl/wp-content/uploads/2015/07/logo-16.png">    
+    <link rel="apple-touch-icon" href="images/logo-57.png">
+    <link rel="apple-touch-icon" sizes="114x114" href="images/logo-114.png">
+    <link rel="apple-touch-icon" sizes="72x72" href="images/logo-72.png">
+    <link rel="apple-touch-icon" sizes="144x144" href="images/logo-144.png">
+
+  	<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.min.css">
+  	<link rel="stylesheet" href="lib/bootstrap/css/bootstrap-theme.min.css">
+   
+    <link rel="stylesheet" href="lib/bootstrap-switch.css">
+    <link rel="stylesheet" href="lib/bootstrap-slider.css">
+    <link rel="stylesheet" href="lib/bootstrap-timepicker.css">
+    <link rel="stylesheet" href="lib/bootstrap-select.css">
+    <link rel="stylesheet" href="lib/d3-gauge-simple.css">
+    
+   	<style>
+      svg {
+        font: 10px sans-serif;
+      }
+
+      #line_sp, #line_tmp2 { color: red; fill: none;}
+      #line_tmp, .line {
+        fill: none;
+        stroke: #000;
+        stroke-width: 1.5px;
+      }
+
+      .axis path,
+      .axis line {
+        fill: none;
+        stroke: #000;
+        shape-rendering: crispEdges;
+      }
+
+      .temperature { width: 10em; }
+      .temperature input { text-align: right; }
+
+      .tab-content { padding-top: 2em;}
+
+      .svg-container {
+        display: inline-block;
+        position: relative;
+        width: 100%;
+        padding-bottom: 70%;
+        vertical-align: middle;
+        overflow: hidden;
+      }
+
+      .svg-content {
+        display: inline-block;
+        position: relative;
+        height: 25vh;
+        width: 100%;
+        top: 0;
+        left: 0;
+        bottom: 0;
+      }
+
+      .slider-track-high { background: darkgrey; }
+      .slider-selection { background: darkgrey;  }
+      .slider-horizontal { top: 0.4em; }
+
+      .bootstrap-select { width: 25em !important;}
+
+      .help {
+        position: absolute; margin-right: 2em; margin-bottom: 16em;
+      }
+    </style>
+  </head>
+  <body>
+  	<div class="container">
+
+      <p>&nbsp;<p/>
+      <div style="padding-bottom: 7vh;">
+        
+        <div id="gauge_boiler" style="position: absolute; z-index: 100; top: 22vh; left: 3vw;"></div>
+        <div id="gauge_shottimer" style="display: none; position: absolute; z-index: 100; top: 22vh; left: 13vw;"></div>
+
+      <div class="well" style="height: 30vh;">
+        <div id="graph" class="svg-container"></div>
+
+      </div>
+      
+      </div>  
+  
+      <ul id="tabs" class="nav nav-tabs">
+        <li role="presentation" class="active"><a href="#connect" data-toggle="tab">Connect</a></li>
+        <li role="presentation"><a href="#temperature" data-toggle="tab">Temperature</a></li>
+        <li role="presentation"><a href="#pid" data-toggle="tab">PID</a></li>
+        <li role="presentation"><a href="#pressure" data-toggle="tab">Pressure</a></li>
+        <li role="presentation"><a href="#preinfusion" data-toggle="tab">Preinfusion</a></li>
+        <li role="presentation"><a href="#timer" data-toggle="tab">Timer</a></li>
+        <li role="presentation"><a href="#hardware" data-toggle="tab">Hardware</a></li>
+        <li role="presentation"><a href="#about" data-toggle="tab">About</a></li>
+      </ul>
+
+      <div id="my-tab-content" class="tab-content">
+
+        <a href="https://mecoffee.nl" target="_blank" style="position: absolute; right: 5vw;">
+          <img src="images/logo-128.png" style="width: 10vw;" />
+        </a>
+
+        <div class="tab-pane" id="temperature">
+
+          <form class="form-horizontal" role="form">
+    
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Current</label>
+              <div class="col-sm-10">
+              	<div class="input-group temperature">
+                  <input id="mc_tmp" type="number" readonly class="form-control" placeholder="101.00" aria-describedby="basic-addon1">
+                  <span class="input-group-addon" id="basic-addon2">&deg;C</span>
+                </div>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Setpoint</label>
+              <div class="col-sm-10">
+              	<div class="input-group temperature">
+                  <input id="mc_tmpsp" type="number" min="90" max="115" step="0.5" 
+                    pattern="\d+(\.\d{2})?" class="form-control target" placeholder="101.00" 
+                    aria-describedby="basic-addon1" data-scale="100">
+                  <span id="mc_tmpsp_c" class="input-group-addon">&deg;C</span>
+                </div>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Steam</label>
+              <div class="col-sm-10">
+                <div class="input-group temperature">
+                  <input id="mc_tmpstm" type="number" min="115" max="135" step="0.5" 
+                    pattern="\d+(\.\d{2})?" class="form-control target" placeholder="125.00" 
+                    aria-describedby="basic-addon1" data-scale="100">
+                  <span id="mc_tmpstm_c" class="input-group-addon">&deg;C</span>
+                </div>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Continous mode</label>
+              <div class="col-sm-10">
+                <input id="mc_tmpcntns" type="checkbox" name="joop" class="form-control target" value="1">
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Pro Active</label>
+              <div class="col-sm-10">
+                <div class="input-group temperature">
+                  <input class="slider target" id="mc_tmppap" type="text" data-slider-min="0" data-slider-max="100" data-slider-step="1" data-slider-value="0" >
+
+                  <!-- span id="mc_pap_lbl">3</span -->
+
+                  <!-- input id="mc_pap" type="text" class="form-control target" aria-describedby="basic-addon1" -->
+                  <!-- span class="input-group-addon" id="basic-addon2">%</span -->
+                </div>
+              </div>
+            </div>
+
+          </form>
+
+        </form>
+          <div class="help">
+                <a href="https://mecoffee.nl/mebarista/help/temperature/" target="_blank">Help</a>
+            </div>
+
+
+        </div>
+
+
+        <div class="tab-pane" id="pid">
+          <form class="form-horizontal" role="form">
+    
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Proportional</label>
+              <div class="col-sm-10">
+                <input id="mc_pd1p" type="text" class="form-control temperature target" placeholder="25" aria-describedby="basic-addon1">
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Integral</label>
+              <div class="col-sm-10">
+                <input id="mc_pd1i" type="text"  class="form-control temperature target" placeholder="3" aria-describedby="basic-addon1">
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Derative</label>
+              <div class="col-sm-10">
+                <input id="mc_pd1d" type="text"  class="form-control temperature target" placeholder="128" aria-describedby="basic-addon1">
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Interval</label>
+              <div class="col-sm-10">
+                <div class="input-group temperature">
+                  <input class="slider target" id="mc_pd1sz" type="text" data-slider-min="1000" data-slider-max="10000" data-slider-step="500" data-slider-value="0" >
+                </div>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Winddown</label>
+              <div class="col-sm-10">
+                <div class="input-group temperature">
+                  <input class="slider target" id="mc_pd1imx" type="text" data-slider-min="1500" data-slider-max="5000" data-slider-step="100" data-slider-value="0" >
+                </div>
+              </div>
+            </div>
+ 
+          </form>
+
+           </form>
+          <div class="help">
+                <a href="https://mecoffee.nl/mebarista/help/temperature/pid/" target="_blank">Help</a>
+            </div>
+
+        </div>
+
+        <div class="tab-pane" id="pressure">  
+          <form class="form-horizontal" role="form"> 
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Start</label>
+              <div class="col-sm-10">
+              	 <div class="input-group temperature">
+
+                <input id="mc_pp1" type="text" class="form-control target" aria-describedby="basic-addon1">
+                 <span class="input-group-addon" id="basic-addon2">%</span>
+                </div>
+
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">End</label>
+              <div class="col-sm-10">
+              	 <div class="input-group temperature">
+
+                <input id="mc_pp2" type="text" class="form-control target" aria-describedby="basic-addon1">
+                 <span class="input-group-addon" id="basic-addon2">%</span>
+                </div>
+
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Period</label>
+              <div class="col-sm-10">
+              	 <div class="input-group temperature">
+
+                <input id="mc_ppt" type="text" class="form-control target" aria-describedby="basic-addon1">
+                 <span class="input-group-addon" id="basic-addon2">s</span>
+                </div>
+
+              </div>
+            </div>
+          
+          </form>  
+          <div class="help">
+                <a href="https://mecoffee.nl/mebarista/help/pressure/" target="_blank">Help</a>
+            </div>
+
+        </div>
+
+        <div class="tab-pane" id="preinfusion">
+          <form class="form-horizontal" role="form">
+    
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Preinfusion</label>
+              <div class="col-sm-10">
+                <input id="mc_pinbl" class="target" type="checkbox" class="form-control target" >
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Pump time</label>
+              <div class="col-sm-10">
+                <div class="input-group temperature">
+                  <input class="slider target" id="mc_pistrt" type="text" data-slider-min="1" data-slider-max="5" data-slider-step="1" data-slider-value="0" >
+                </div>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Close valve</label>
+              <div class="col-sm-10">
+                <input id="mc_pivlv" class="target" type="checkbox" class="form-control target" >
+              </div>
+            </div>
+
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Pause time</label>
+              <div class="col-sm-10">
+                <div class="input-group temperature">
+                  <input class="slider target" id="mc_piprd" type="text" data-slider-min="1" data-slider-max="10" data-slider-step="1" data-slider-value="0" >
+                </div>
+              </div>
+            </div>
+
+          </form>
+
+           </form>
+          <div class="help">
+                <a href="https://mecoffee.nl/mebarista/help/preinfusion/" target="_blank">Help</a>
+            </div>
+
+        </div>
+
+        <div class="tab-pane" id="timer">
+
+          <form class="form-horizontal" role="form">            
+    
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Wakeup</label>
+              <div class="col-sm-10">
+                <input id="mc_tmrwnbl" class="target" type="checkbox" class="form-control target" >
+              </div>
+            </div>
+
+             <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">At</label>
+              <div class="col-sm-10">
+                <div class="input-group temperature">
+
+                  <input id="mc_tmron" type="text" class="form-control input-small timepicker target"  aria-describedby="basic-addon1">
+                  <span class="input-group-addon" id="basic-addon2"><i class="glyphicon glyphicon-time" aria-hidden="true"></i></span>
+
+                </div>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Shutdown</label>
+              <div class="col-sm-10">
+                <input id="mc_tmrsnbl" class="target" type="checkbox" class="form-control target" >
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">At</label>
+              <div class="col-sm-10">
+                <div class="input-group temperature">
+
+                  <input id="mc_tmroff" type="text" class="form-control input-small timepicker target"  aria-describedby="basic-addon1">
+                  <span class="input-group-addon" id="basic-addon2"><i class="glyphicon glyphicon-time" aria-hidden="true"></i></span>
+
+                </div>
+              </div>   
+            </div>
+
+            
+
+          </form>
+          <div class="help">
+                <a href="https://mecoffee.nl/mebarista/help/timer/" target="_blank">Help</a>
+            </div>
+        </div>
+
+        <div class="tab-pane" id="hardware">
+          <form class="form-horizontal" role="form">
+    
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Installed a timer</label>
+              <div class="col-sm-10">
+                <input id="mc_tmrpwr" class="target" type="checkbox" class="form-control target" >
+              </div>
+            </div>
+          </form>
+        </div>
+
+        <div class="tab-pane active" id="connect">
+          <form class="form-horizontal" role="form">
+            
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Show</label>
+              <div class="col-sm-10">
+  
+                <select id="device" class="selectpicker">
+                  <optgroup id="devices" label="Bluetooth devices">
+                    <option id="devices_msg" value="none">Not available: use as a Google Chrome/App extension to access Bluetooth devices</option>
+                  </optgroup>
+                  <optgroup label="Demos">
+                    <option value="share_56273d393cab0" selected>Silvia meCoffee vs Factory - warmup</option>
+                    <!-- option value="share_55fc39ec76b7b">Silvia meCoffee vs Factory - espresso shot</option>
+                    <option value="share_55fc667737cc9">Vibiemme Domobar meCoffee - warmup</option !-->
+                  </optgroup>
+                </select>
+
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Speed</label>
+              <div class="col-sm-10">
+                  <input class="slider" id="demo_speed" type="text" data-slider-min="1" data-slider-max="50" data-slider-step="1" data-slider-value="5" >
+              </div>
+            </div>
+
+            <p style="height: 2em;"></p>
+            
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp"></label>
+              <div class="col-sm-10">
+            
+                  <button id="start" class="btn btn-primary">
+                    <span id="start_icon" class="glyphicon glyphicon-play" aria-hidden="true"></span>
+                    &nbsp;&nbsp;
+                    <span id="start_label">Start</span>
+                  </button>
+
+              </div>
+            </div>
+            
+          </form>
+
+        </div>
+
+        <div class="tab-pane" id="about">
+          <form class="form-horizontal" role="form">
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Website</label>
+              <div class="col-sm-10">
+                <a href="https://mecoffee.nl" target="_blank">mecoffee.nl</a>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp"></label>
+              <div class="col-sm-10">
+            
+                  <button id="share" class="btn btn-primary">
+                    <span id="share_icon" class="glyphicon glyphicon-share" aria-hidden="true"></span>
+                    &nbsp;&nbsp;
+                    <span id="start_label">Share</span>
+                  </button>
+                  <span id="share_result" style="margin-left: 2em;">...</span>
+
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="mc_tmp">Version</label>
+              <div class="col-sm-10">
+                0.2.1<br/>Support for 0.1 second preinfusion timing with new firmware<br/>
+                0.2.0<br/>Support for new firmware : updated preinfusion scale to milliseconds.<br/>
+                0.1.8<br/>Fixed connectivity, shutdown timer, shot timer<br/>
+                0.1.7<br/>
+                Added Boiler Gauge, Shot Timer and remote loading of meBarista logs<br/>
+                0.1.6<br/>
+                Added the right scale to the steam control<br/>
+                0.1.5<br/>
+                Added steam control, cleaned up<br/>
+                0.1.4<br/>
+                Fixed pressure controls again<br/>
+                0.1.3<br/>
+                Added about-tab</br>
+                <br/>  
+                0.1.2<br/>
+                Fixed preinfusion and pressure controls<br/>
+                <br/>
+                0.1.1<br/>
+                Initial public version<br/>
+              </div>
+            </div>
+
+
+          </form>
+        </div>
+
+    </div>
+
+	</div>
+
+
+    <script src="lib/jquery-1.11.2.min.js"></script>
+    <script src="lib/bootstrap/js/bootstrap.min.js"></script>
+    <script src="lib/bootstrap-switch.js"></script>
+    <script src="lib/bootstrap-slider.min.js"></script>
+    <script src="lib/bootstrap-timepicker.js"></script>
+    <script src="lib/bootstrap-select.js"></script>
+    <script src="lib/d3.v3.min.js"></script>
+    <script src="lib/d3-gauge.js"></script>
+
+    <script src="graph.js"></script>
+    <script src="mecoffee.js"></script>
+    
+  </body>
+</html>

+ 260 - 0
lib/bootstrap-select.css

@@ -0,0 +1,260 @@
+/*!
+ * Bootstrap-select v1.6.3 (http://silviomoreto.github.io/bootstrap-select/)
+ *
+ * Copyright 2013-2014 bootstrap-select
+ * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
+ */
+
+.bootstrap-select {
+  /*width: 220px\9; IE8 and below*/
+  width: 220px \0;
+  /*IE9 and below*/
+}
+.bootstrap-select > .btn {
+  width: 100%;
+  padding-right: 25px;
+}
+.error .bootstrap-select .btn {
+  border: 1px solid #b94a48;
+}
+.control-group.error .bootstrap-select .dropdown-toggle {
+  border-color: #b94a48;
+}
+.bootstrap-select.fit-width {
+  width: auto !important;
+}
+.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) {
+  width: 220px;
+}
+.bootstrap-select .btn:focus {
+  outline: thin dotted #333333 !important;
+  outline: 5px auto -webkit-focus-ring-color !important;
+  outline-offset: -2px;
+}
+.bootstrap-select.form-control {
+  margin-bottom: 0;
+  padding: 0;
+  border: none;
+}
+.bootstrap-select.form-control:not([class*="col-"]) {
+  width: 100%;
+}
+.bootstrap-select.btn-group:not(.input-group-btn),
+.bootstrap-select.btn-group[class*="col-"] {
+  float: none;
+  display: inline-block;
+  margin-left: 0;
+}
+.bootstrap-select.btn-group.dropdown-menu-right,
+.bootstrap-select.btn-group[class*="col-"].dropdown-menu-right,
+.row-fluid .bootstrap-select.btn-group[class*="col-"].dropdown-menu-right {
+  float: right;
+}
+.form-search .bootstrap-select.btn-group,
+.form-inline .bootstrap-select.btn-group,
+.form-horizontal .bootstrap-select.btn-group,
+.form-group .bootstrap-select.btn-group {
+  margin-bottom: 0;
+}
+.form-group-lg .bootstrap-select.btn-group.form-control,
+.form-group-sm .bootstrap-select.btn-group.form-control {
+  padding: 0;
+}
+.form-inline .bootstrap-select.btn-group .form-control {
+  width: 100%;
+}
+.input-append .bootstrap-select.btn-group {
+  margin-left: -1px;
+}
+.input-prepend .bootstrap-select.btn-group {
+  margin-right: -1px;
+}
+.bootstrap-select.btn-group > .disabled {
+  cursor: not-allowed;
+}
+.bootstrap-select.btn-group > .disabled:focus {
+  outline: none !important;
+}
+.bootstrap-select.btn-group .btn .filter-option {
+  display: inline-block;
+  overflow: hidden;
+  width: 100%;
+  text-align: left;
+}
+.bootstrap-select.btn-group .btn .caret {
+  position: absolute;
+  top: 50%;
+  right: 12px;
+  margin-top: -2px;
+  vertical-align: middle;
+}
+.bootstrap-select.btn-group[class*="col-"] .btn {
+  width: 100%;
+}
+.bootstrap-select.btn-group .dropdown-menu {
+  min-width: 100%;
+  z-index: 1035;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bootstrap-select.btn-group .dropdown-menu.inner {
+  position: static;
+  border: 0;
+  padding: 0;
+  margin: 0;
+  border-radius: 0;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+.bootstrap-select.btn-group .dropdown-menu li {
+  position: relative;
+}
+.bootstrap-select.btn-group .dropdown-menu li:not(.disabled) a:hover small,
+.bootstrap-select.btn-group .dropdown-menu li:not(.disabled) a:focus small,
+.bootstrap-select.btn-group .dropdown-menu li.active:not(.disabled) a small {
+  color: #64b1d8;
+  color: rgba(100, 177, 216, 0.4);
+}
+.bootstrap-select.btn-group .dropdown-menu li.disabled a {
+  cursor: not-allowed;
+}
+.bootstrap-select.btn-group .dropdown-menu li a {
+  cursor: pointer;
+}
+.bootstrap-select.btn-group .dropdown-menu li a.opt {
+  position: relative;
+  padding-left: 2.25em;
+}
+.bootstrap-select.btn-group .dropdown-menu li a span.check-mark {
+  display: none;
+}
+.bootstrap-select.btn-group .dropdown-menu li a span.text {
+  display: inline-block;
+}
+.bootstrap-select.btn-group .dropdown-menu li small {
+  padding-left: 0.5em;
+}
+.bootstrap-select.btn-group .dropdown-menu .notify {
+  position: absolute;
+  bottom: 5px;
+  width: 96%;
+  margin: 0 2%;
+  min-height: 26px;
+  padding: 3px 5px;
+  background: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+  pointer-events: none;
+  opacity: 0.9;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bootstrap-select.btn-group .no-results {
+  padding: 3px;
+  background: #f5f5f5;
+  margin: 0 5px;
+}
+.bootstrap-select.btn-group.fit-width .btn .filter-option {
+  position: static;
+}
+.bootstrap-select.btn-group.fit-width .btn .caret {
+  position: static;
+  top: auto;
+  margin-top: -1px;
+}
+.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark {
+  position: absolute;
+  display: inline-block;
+  right: 15px;
+  margin-top: 5px;
+}
+.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text {
+  margin-right: 34px;
+}
+.bootstrap-select.show-menu-arrow.open > .btn {
+  z-index: 1035 + 1;
+}
+.bootstrap-select.show-menu-arrow .dropdown-toggle:before {
+  content: '';
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom-width: 7px;
+  border-bottom-style: solid;
+  border-bottom-color: #cccccc;
+  border-bottom-color: rgba(204, 204, 204, 0.2);
+  position: absolute;
+  bottom: -4px;
+  left: 9px;
+  display: none;
+}
+.bootstrap-select.show-menu-arrow .dropdown-toggle:after {
+  content: '';
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid white;
+  position: absolute;
+  bottom: -4px;
+  left: 10px;
+  display: none;
+}
+.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before {
+  bottom: auto;
+  top: -3px;
+  border-bottom: 0;
+  border-top-width: 7px;
+  border-top-style: solid;
+  border-top-color: #cccccc;
+  border-top-color: rgba(204, 204, 204, 0.2);
+}
+.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after {
+  bottom: auto;
+  top: -3px;
+  border-top: 6px solid white;
+  border-bottom: 0;
+}
+.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before {
+  right: 12px;
+  left: auto;
+}
+.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after {
+  right: 13px;
+  left: auto;
+}
+.bootstrap-select.show-menu-arrow.open > .dropdown-toggle:before,
+.bootstrap-select.show-menu-arrow.open > .dropdown-toggle:after {
+  display: block;
+}
+.bs-searchbox,
+.bs-actionsbox {
+  padding: 4px 8px;
+}
+.bs-actionsbox {
+  float: left;
+  width: 100%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bs-actionsbox .btn-group button {
+  width: 50%;
+}
+.bs-searchbox + .bs-actionsbox {
+  padding: 0 8px 4px;
+}
+.bs-searchbox input.form-control {
+  margin-bottom: 0;
+  width: 100%;
+}
+.mobile-device {
+  position: absolute;
+  top: 0;
+  left: 0;
+  display: block !important;
+  width: 100%;
+  height: 100% !important;
+  opacity: 0;
+}
+/*# sourceMappingURL=bootstrap-select.css.map */

文件差异内容过多而无法显示
+ 1215 - 0
lib/bootstrap-select.js


+ 248 - 0
lib/bootstrap-slider.css

@@ -0,0 +1,248 @@
+/*! =======================================================
+                      VERSION  4.7.4              
+========================================================= */
+/*! =========================================================
+ * bootstrap-slider.js
+ *
+ * Maintainers:
+ *		Kyle Kemp
+ *			- Twitter: @seiyria
+ *			- Github:  seiyria
+ *		Rohit Kalkur
+ *			- Twitter: @Rovolutionary
+ *			- Github:  rovolution
+ *
+ * =========================================================
+ *
+ * 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.
+ * ========================================================= */
+.slider {
+  display: inline-block;
+  vertical-align: middle;
+  position: relative;
+}
+.slider.slider-horizontal {
+  width: 210px;
+  height: 20px;
+}
+.slider.slider-horizontal .slider-track {
+  height: 10px;
+  width: 100%;
+  margin-top: -5px;
+  top: 50%;
+  left: 0;
+}
+.slider.slider-horizontal .slider-selection,
+.slider.slider-horizontal .slider-track-low,
+.slider.slider-horizontal .slider-track-high {
+  height: 100%;
+  top: 0;
+  bottom: 0;
+}
+.slider.slider-horizontal .slider-tick,
+.slider.slider-horizontal .slider-handle {
+  margin-left: -10px;
+  margin-top: -5px;
+}
+.slider.slider-horizontal .slider-tick.triangle,
+.slider.slider-horizontal .slider-handle.triangle {
+  border-width: 0 10px 10px 10px;
+  width: 0;
+  height: 0;
+  border-bottom-color: #0480be;
+  margin-top: 0;
+}
+.slider.slider-horizontal .slider-tick-label-container {
+  white-space: nowrap;
+}
+.slider.slider-horizontal .slider-tick-label-container .slider-tick-label {
+  margin-top: 24px;
+  display: inline-block;
+  text-align: center;
+}
+.slider.slider-vertical {
+  height: 210px;
+  width: 20px;
+}
+.slider.slider-vertical .slider-track {
+  width: 10px;
+  height: 100%;
+  margin-left: -5px;
+  left: 50%;
+  top: 0;
+}
+.slider.slider-vertical .slider-selection {
+  width: 100%;
+  left: 0;
+  top: 0;
+  bottom: 0;
+}
+.slider.slider-vertical .slider-track-low,
+.slider.slider-vertical .slider-track-high {
+  width: 100%;
+  left: 0;
+  right: 0;
+}
+.slider.slider-vertical .slider-tick,
+.slider.slider-vertical .slider-handle {
+  margin-left: -5px;
+  margin-top: -10px;
+}
+.slider.slider-vertical .slider-tick.triangle,
+.slider.slider-vertical .slider-handle.triangle {
+  border-width: 10px 0 10px 10px;
+  width: 1px;
+  height: 1px;
+  border-left-color: #0480be;
+  margin-left: 0;
+}
+.slider.slider-disabled .slider-handle {
+  background-image: -webkit-linear-gradient(top, #dfdfdf 0%, #bebebe 100%);
+  background-image: -o-linear-gradient(top, #dfdfdf 0%, #bebebe 100%);
+  background-image: linear-gradient(to bottom, #dfdfdf 0%, #bebebe 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf', endColorstr='#ffbebebe', GradientType=0);
+}
+.slider.slider-disabled .slider-track {
+  background-image: -webkit-linear-gradient(top, #e5e5e5 0%, #e9e9e9 100%);
+  background-image: -o-linear-gradient(top, #e5e5e5 0%, #e9e9e9 100%);
+  background-image: linear-gradient(to bottom, #e5e5e5 0%, #e9e9e9 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5', endColorstr='#ffe9e9e9', GradientType=0);
+  cursor: not-allowed;
+}
+.slider input {
+  display: none;
+}
+.slider .tooltip.top {
+  margin-top: -36px;
+}
+.slider .tooltip-inner {
+  white-space: nowrap;
+}
+.slider .hide {
+  display: none;
+}
+.slider-track {
+  position: absolute;
+  cursor: pointer;
+  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #f9f9f9 100%);
+  background-image: -o-linear-gradient(top, #f5f5f5 0%, #f9f9f9 100%);
+  background-image: linear-gradient(to bottom, #f5f5f5 0%, #f9f9f9 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  border-radius: 4px;
+}
+.slider-selection {
+  position: absolute;
+  background-image: -webkit-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%);
+  background-image: -o-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%);
+  background-image: linear-gradient(to bottom, #f9f9f9 0%, #f5f5f5 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  border-radius: 4px;
+}
+.slider-selection.tick-slider-selection {
+  background-image: -webkit-linear-gradient(top, #89cdef 0%, #81bfde 100%);
+  background-image: -o-linear-gradient(top, #89cdef 0%, #81bfde 100%);
+  background-image: linear-gradient(to bottom, #89cdef 0%, #81bfde 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef', endColorstr='#ff81bfde', GradientType=0);
+}
+.slider-track-low,
+.slider-track-high {
+  position: absolute;
+  background: transparent;
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  border-radius: 4px;
+}
+.slider-handle {
+  position: absolute;
+  width: 20px;
+  height: 20px;
+  background-color: #337ab7;
+  background-image: -webkit-linear-gradient(top, #149bdf 0%, #0480be 100%);
+  background-image: -o-linear-gradient(top, #149bdf 0%, #0480be 100%);
+  background-image: linear-gradient(to bottom, #149bdf 0%, #0480be 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+  filter: none;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  border: 0px solid transparent;
+}
+.slider-handle.round {
+  border-radius: 50%;
+}
+.slider-handle.triangle {
+  background: transparent none;
+}
+.slider-handle.custom {
+  background: transparent none;
+}
+.slider-handle.custom::before {
+  line-height: 20px;
+  font-size: 20px;
+  content: '\2605';
+  color: #726204;
+}
+.slider-tick {
+  position: absolute;
+  width: 20px;
+  height: 20px;
+  background-image: -webkit-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%);
+  background-image: -o-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%);
+  background-image: linear-gradient(to bottom, #f9f9f9 0%, #f5f5f5 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  filter: none;
+  opacity: 0.8;
+  border: 0px solid transparent;
+}
+.slider-tick.round {
+  border-radius: 50%;
+}
+.slider-tick.triangle {
+  background: transparent none;
+}
+.slider-tick.custom {
+  background: transparent none;
+}
+.slider-tick.custom::before {
+  line-height: 20px;
+  font-size: 20px;
+  content: '\2605';
+  color: #726204;
+}
+.slider-tick.in-selection {
+  background-image: -webkit-linear-gradient(top, #89cdef 0%, #81bfde 100%);
+  background-image: -o-linear-gradient(top, #89cdef 0%, #81bfde 100%);
+  background-image: linear-gradient(to bottom, #89cdef 0%, #81bfde 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef', endColorstr='#ff81bfde', GradientType=0);
+  opacity: 1;
+}

文件差异内容过多而无法显示
+ 29 - 0
lib/bootstrap-slider.min.js


+ 196 - 0
lib/bootstrap-switch.css

@@ -0,0 +1,196 @@
+/* ========================================================================
+ * bootstrap-switch - v3.3.2
+ * http://www.bootstrap-switch.org
+ * ========================================================================
+ * Copyright 2012-2013 Mattia Larentis
+ *
+ * ========================================================================
+ * 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.
+ * ========================================================================
+ */
+
+.bootstrap-switch {
+  display: inline-block;
+  direction: ltr;
+  cursor: pointer;
+  border-radius: 4px;
+  border: 1px solid;
+  border-color: #cccccc;
+  position: relative;
+  text-align: left;
+  overflow: hidden;
+  line-height: 8px;
+  z-index: 0;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  vertical-align: middle;
+  -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+}
+.bootstrap-switch .bootstrap-switch-container {
+  display: inline-block;
+  top: 0;
+  border-radius: 4px;
+  -webkit-transform: translate3d(0, 0, 0);
+  transform: translate3d(0, 0, 0);
+}
+.bootstrap-switch .bootstrap-switch-handle-on,
+.bootstrap-switch .bootstrap-switch-handle-off,
+.bootstrap-switch .bootstrap-switch-label {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  cursor: pointer;
+  display: inline-block !important;
+  height: 100%;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 20px;
+}
+.bootstrap-switch .bootstrap-switch-handle-on,
+.bootstrap-switch .bootstrap-switch-handle-off {
+  text-align: center;
+  z-index: 1;
+}
+.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary,
+.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary {
+  color: #fff;
+  background: #428bca;
+}
+.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info,
+.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info {
+  color: #fff;
+  background: #5bc0de;
+}
+.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success,
+.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success {
+  color: #fff;
+  background: #5cb85c;
+}
+.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning,
+.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning {
+  background: #f0ad4e;
+  color: #fff;
+}
+.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger,
+.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger {
+  color: #fff;
+  background: #d9534f;
+}
+.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default,
+.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default {
+  color: #000;
+  background: #eeeeee;
+}
+.bootstrap-switch .bootstrap-switch-label {
+  text-align: center;
+  margin-top: -1px;
+  margin-bottom: -1px;
+  z-index: 100;
+  color: #333333;
+  background: #ffffff;
+}
+.bootstrap-switch .bootstrap-switch-handle-on {
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}
+.bootstrap-switch .bootstrap-switch-handle-off {
+  border-bottom-right-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.bootstrap-switch input[type='radio'],
+.bootstrap-switch input[type='checkbox'] {
+  position: absolute !important;
+  top: 0;
+  left: 0;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  z-index: -1;
+}
+.bootstrap-switch input[type='radio'].form-control,
+.bootstrap-switch input[type='checkbox'].form-control {
+  height: auto;
+}
+.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label {
+  padding: 1px 5px;
+  font-size: 12px;
+  line-height: 1.5;
+}
+.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+}
+.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label {
+  padding: 6px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+}
+.bootstrap-switch.bootstrap-switch-disabled,
+.bootstrap-switch.bootstrap-switch-readonly,
+.bootstrap-switch.bootstrap-switch-indeterminate {
+  cursor: default !important;
+}
+.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,
+.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,
+.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,
+.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label,
+.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+  cursor: default !important;
+}
+.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container {
+  -webkit-transition: margin-left 0.5s;
+  transition: margin-left 0.5s;
+}
+.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+  border-bottom-right-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off {
+  border-bottom-right-radius: 0;
+  border-top-right-radius: 0;
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}
+.bootstrap-switch.bootstrap-switch-focused {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label,
+.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label {
+  border-bottom-right-radius: 3px;
+  border-top-right-radius: 3px;
+}
+.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label,
+.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label {
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}

+ 710 - 0
lib/bootstrap-switch.js

@@ -0,0 +1,710 @@
+/* ========================================================================
+ * bootstrap-switch - v3.3.2
+ * http://www.bootstrap-switch.org
+ * ========================================================================
+ * Copyright 2012-2013 Mattia Larentis
+ *
+ * ========================================================================
+ * 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 __slice = [].slice;
+
+  (function($, window) {
+    "use strict";
+    var BootstrapSwitch;
+    BootstrapSwitch = (function() {
+      function BootstrapSwitch(element, options) {
+        if (options == null) {
+          options = {};
+        }
+        this.$element = $(element);
+        this.options = $.extend({}, $.fn.bootstrapSwitch.defaults, {
+          state: this.$element.is(":checked"),
+          size: this.$element.data("size"),
+          animate: this.$element.data("animate"),
+          disabled: this.$element.is(":disabled"),
+          readonly: this.$element.is("[readonly]"),
+          indeterminate: this.$element.data("indeterminate"),
+          inverse: this.$element.data("inverse"),
+          radioAllOff: this.$element.data("radio-all-off"),
+          onColor: this.$element.data("on-color"),
+          offColor: this.$element.data("off-color"),
+          onText: this.$element.data("on-text"),
+          offText: this.$element.data("off-text"),
+          labelText: this.$element.data("label-text"),
+          handleWidth: this.$element.data("handle-width"),
+          labelWidth: this.$element.data("label-width"),
+          baseClass: this.$element.data("base-class"),
+          wrapperClass: this.$element.data("wrapper-class")
+        }, options);
+        this.$wrapper = $("<div>", {
+          "class": (function(_this) {
+            return function() {
+              var classes;
+              classes = ["" + _this.options.baseClass].concat(_this._getClasses(_this.options.wrapperClass));
+              classes.push(_this.options.state ? "" + _this.options.baseClass + "-on" : "" + _this.options.baseClass + "-off");
+              if (_this.options.size != null) {
+                classes.push("" + _this.options.baseClass + "-" + _this.options.size);
+              }
+              if (_this.options.disabled) {
+                classes.push("" + _this.options.baseClass + "-disabled");
+              }
+              if (_this.options.readonly) {
+                classes.push("" + _this.options.baseClass + "-readonly");
+              }
+              if (_this.options.indeterminate) {
+                classes.push("" + _this.options.baseClass + "-indeterminate");
+              }
+              if (_this.options.inverse) {
+                classes.push("" + _this.options.baseClass + "-inverse");
+              }
+              if (_this.$element.attr("id")) {
+                classes.push("" + _this.options.baseClass + "-id-" + (_this.$element.attr("id")));
+              }
+              return classes.join(" ");
+            };
+          })(this)()
+        });
+        this.$container = $("<div>", {
+          "class": "" + this.options.baseClass + "-container"
+        });
+        this.$on = $("<span>", {
+          html: this.options.onText,
+          "class": "" + this.options.baseClass + "-handle-on " + this.options.baseClass + "-" + this.options.onColor
+        });
+        this.$off = $("<span>", {
+          html: this.options.offText,
+          "class": "" + this.options.baseClass + "-handle-off " + this.options.baseClass + "-" + this.options.offColor
+        });
+        this.$label = $("<span>", {
+          html: this.options.labelText,
+          "class": "" + this.options.baseClass + "-label"
+        });
+        this.$element.on("init.bootstrapSwitch", (function(_this) {
+          return function() {
+            return _this.options.onInit.apply(element, arguments);
+          };
+        })(this));
+        this.$element.on("switchChange.bootstrapSwitch", (function(_this) {
+          return function() {
+            return _this.options.onSwitchChange.apply(element, arguments);
+          };
+        })(this));
+        this.$container = this.$element.wrap(this.$container).parent();
+        this.$wrapper = this.$container.wrap(this.$wrapper).parent();
+        this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off);
+        if (this.options.indeterminate) {
+          this.$element.prop("indeterminate", true);
+        }
+        this._init();
+        this._elementHandlers();
+        this._handleHandlers();
+        this._labelHandlers();
+        this._formHandler();
+        this._externalLabelHandler();
+        this.$element.trigger("init.bootstrapSwitch");
+      }
+
+      BootstrapSwitch.prototype._constructor = BootstrapSwitch;
+
+      BootstrapSwitch.prototype.state = function(value, skip) {
+        if (typeof value === "undefined") {
+          return this.options.state;
+        }
+        if (this.options.disabled || this.options.readonly) {
+          return this.$element;
+        }
+        if (this.options.state && !this.options.radioAllOff && this.$element.is(":radio")) {
+          return this.$element;
+        }
+        if (this.options.indeterminate) {
+          this.indeterminate(false);
+        }
+        value = !!value;
+        this.$element.prop("checked", value).trigger("change.bootstrapSwitch", skip);
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.toggleState = function(skip) {
+        if (this.options.disabled || this.options.readonly) {
+          return this.$element;
+        }
+        if (this.options.indeterminate) {
+          this.indeterminate(false);
+          return this.state(true);
+        } else {
+          return this.$element.prop("checked", !this.options.state).trigger("change.bootstrapSwitch", skip);
+        }
+      };
+
+      BootstrapSwitch.prototype.size = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.size;
+        }
+        if (this.options.size != null) {
+          this.$wrapper.removeClass("" + this.options.baseClass + "-" + this.options.size);
+        }
+        if (value) {
+          this.$wrapper.addClass("" + this.options.baseClass + "-" + value);
+        }
+        this._width();
+        this._containerPosition();
+        this.options.size = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.animate = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.animate;
+        }
+        value = !!value;
+        if (value === this.options.animate) {
+          return this.$element;
+        }
+        return this.toggleAnimate();
+      };
+
+      BootstrapSwitch.prototype.toggleAnimate = function() {
+        this.options.animate = !this.options.animate;
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-animate");
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.disabled = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.disabled;
+        }
+        value = !!value;
+        if (value === this.options.disabled) {
+          return this.$element;
+        }
+        return this.toggleDisabled();
+      };
+
+      BootstrapSwitch.prototype.toggleDisabled = function() {
+        this.options.disabled = !this.options.disabled;
+        this.$element.prop("disabled", this.options.disabled);
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-disabled");
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.readonly = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.readonly;
+        }
+        value = !!value;
+        if (value === this.options.readonly) {
+          return this.$element;
+        }
+        return this.toggleReadonly();
+      };
+
+      BootstrapSwitch.prototype.toggleReadonly = function() {
+        this.options.readonly = !this.options.readonly;
+        this.$element.prop("readonly", this.options.readonly);
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-readonly");
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.indeterminate = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.indeterminate;
+        }
+        value = !!value;
+        if (value === this.options.indeterminate) {
+          return this.$element;
+        }
+        return this.toggleIndeterminate();
+      };
+
+      BootstrapSwitch.prototype.toggleIndeterminate = function() {
+        this.options.indeterminate = !this.options.indeterminate;
+        this.$element.prop("indeterminate", this.options.indeterminate);
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-indeterminate");
+        this._containerPosition();
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.inverse = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.inverse;
+        }
+        value = !!value;
+        if (value === this.options.inverse) {
+          return this.$element;
+        }
+        return this.toggleInverse();
+      };
+
+      BootstrapSwitch.prototype.toggleInverse = function() {
+        var $off, $on;
+        this.$wrapper.toggleClass("" + this.options.baseClass + "-inverse");
+        $on = this.$on.clone(true);
+        $off = this.$off.clone(true);
+        this.$on.replaceWith($off);
+        this.$off.replaceWith($on);
+        this.$on = $off;
+        this.$off = $on;
+        this.options.inverse = !this.options.inverse;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.onColor = function(value) {
+        var color;
+        color = this.options.onColor;
+        if (typeof value === "undefined") {
+          return color;
+        }
+        if (color != null) {
+          this.$on.removeClass("" + this.options.baseClass + "-" + color);
+        }
+        this.$on.addClass("" + this.options.baseClass + "-" + value);
+        this.options.onColor = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.offColor = function(value) {
+        var color;
+        color = this.options.offColor;
+        if (typeof value === "undefined") {
+          return color;
+        }
+        if (color != null) {
+          this.$off.removeClass("" + this.options.baseClass + "-" + color);
+        }
+        this.$off.addClass("" + this.options.baseClass + "-" + value);
+        this.options.offColor = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.onText = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.onText;
+        }
+        this.$on.html(value);
+        this._width();
+        this._containerPosition();
+        this.options.onText = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.offText = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.offText;
+        }
+        this.$off.html(value);
+        this._width();
+        this._containerPosition();
+        this.options.offText = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.labelText = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.labelText;
+        }
+        this.$label.html(value);
+        this._width();
+        this.options.labelText = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.handleWidth = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.handleWidth;
+        }
+        this.options.handleWidth = value;
+        this._width();
+        this._containerPosition();
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.labelWidth = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.labelWidth;
+        }
+        this.options.labelWidth = value;
+        this._width();
+        this._containerPosition();
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.baseClass = function(value) {
+        return this.options.baseClass;
+      };
+
+      BootstrapSwitch.prototype.wrapperClass = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.wrapperClass;
+        }
+        if (!value) {
+          value = $.fn.bootstrapSwitch.defaults.wrapperClass;
+        }
+        this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(" "));
+        this.$wrapper.addClass(this._getClasses(value).join(" "));
+        this.options.wrapperClass = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.radioAllOff = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.radioAllOff;
+        }
+        value = !!value;
+        if (value === this.options.radioAllOff) {
+          return this.$element;
+        }
+        this.options.radioAllOff = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.onInit = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.onInit;
+        }
+        if (!value) {
+          value = $.fn.bootstrapSwitch.defaults.onInit;
+        }
+        this.options.onInit = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.onSwitchChange = function(value) {
+        if (typeof value === "undefined") {
+          return this.options.onSwitchChange;
+        }
+        if (!value) {
+          value = $.fn.bootstrapSwitch.defaults.onSwitchChange;
+        }
+        this.options.onSwitchChange = value;
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype.destroy = function() {
+        var $form;
+        $form = this.$element.closest("form");
+        if ($form.length) {
+          $form.off("reset.bootstrapSwitch").removeData("bootstrap-switch");
+        }
+        this.$container.children().not(this.$element).remove();
+        this.$element.unwrap().unwrap().off(".bootstrapSwitch").removeData("bootstrap-switch");
+        return this.$element;
+      };
+
+      BootstrapSwitch.prototype._width = function() {
+        var $handles, handleWidth;
+        $handles = this.$on.add(this.$off);
+        $handles.add(this.$label).css("width", "");
+        handleWidth = this.options.handleWidth === "auto" ? Math.max(this.$on.width(), this.$off.width()) : this.options.handleWidth;
+        $handles.width(handleWidth);
+        this.$label.width((function(_this) {
+          return function(index, width) {
+            if (_this.options.labelWidth !== "auto") {
+              return _this.options.labelWidth;
+            }
+            if (width < handleWidth) {
+              return handleWidth;
+            } else {
+              return width;
+            }
+          };
+        })(this));
+        this._handleWidth = this.$on.outerWidth();
+        this._labelWidth = this.$label.outerWidth();
+        this.$container.width((this._handleWidth * 2) + this._labelWidth);
+        return this.$wrapper.width(this._handleWidth + this._labelWidth);
+      };
+
+      BootstrapSwitch.prototype._containerPosition = function(state, callback) {
+        if (state == null) {
+          state = this.options.state;
+        }
+        this.$container.css("margin-left", (function(_this) {
+          return function() {
+            var values;
+            values = [0, "-" + _this._handleWidth + "px"];
+            if (_this.options.indeterminate) {
+              return "-" + (_this._handleWidth / 2) + "px";
+            }
+            if (state) {
+              if (_this.options.inverse) {
+                return values[1];
+              } else {
+                return values[0];
+              }
+            } else {
+              if (_this.options.inverse) {
+                return values[0];
+              } else {
+                return values[1];
+              }
+            }
+          };
+        })(this));
+        if (!callback) {
+          return;
+        }
+        return setTimeout(function() {
+          return callback();
+        }, 50);
+      };
+
+      BootstrapSwitch.prototype._init = function() {
+        var init, initInterval;
+        init = (function(_this) {
+          return function() {
+            _this._width();
+            return _this._containerPosition(null, function() {
+              if (_this.options.animate) {
+                return _this.$wrapper.addClass("" + _this.options.baseClass + "-animate");
+              }
+            });
+          };
+        })(this);
+        if (this.$wrapper.is(":visible")) {
+          return init();
+        }
+        return initInterval = window.setInterval((function(_this) {
+          return function() {
+            if (_this.$wrapper.is(":visible")) {
+              init();
+              return window.clearInterval(initInterval);
+            }
+          };
+        })(this), 50);
+      };
+
+      BootstrapSwitch.prototype._elementHandlers = function() {
+        return this.$element.on({
+          "change.bootstrapSwitch": (function(_this) {
+            return function(e, skip) {
+              var state;
+              e.preventDefault();
+              e.stopImmediatePropagation();
+              state = _this.$element.is(":checked");
+              _this._containerPosition(state);
+              if (state === _this.options.state) {
+                return;
+              }
+              _this.options.state = state;
+              _this.$wrapper.toggleClass("" + _this.options.baseClass + "-off").toggleClass("" + _this.options.baseClass + "-on");
+              if (!skip) {
+                if (_this.$element.is(":radio")) {
+                  $("[name='" + (_this.$element.attr('name')) + "']").not(_this.$element).prop("checked", false).trigger("change.bootstrapSwitch", true);
+                }
+                return _this.$element.trigger("switchChange.bootstrapSwitch", [state]);
+              }
+            };
+          })(this),
+          "focus.bootstrapSwitch": (function(_this) {
+            return function(e) {
+              e.preventDefault();
+              return _this.$wrapper.addClass("" + _this.options.baseClass + "-focused");
+            };
+          })(this),
+          "blur.bootstrapSwitch": (function(_this) {
+            return function(e) {
+              e.preventDefault();
+              return _this.$wrapper.removeClass("" + _this.options.baseClass + "-focused");
+            };
+          })(this),
+          "keydown.bootstrapSwitch": (function(_this) {
+            return function(e) {
+              if (!e.which || _this.options.disabled || _this.options.readonly) {
+                return;
+              }
+              switch (e.which) {
+                case 37:
+                  e.preventDefault();
+                  e.stopImmediatePropagation();
+                  return _this.state(false);
+                case 39:
+                  e.preventDefault();
+                  e.stopImmediatePropagation();
+                  return _this.state(true);
+              }
+            };
+          })(this)
+        });
+      };
+
+      BootstrapSwitch.prototype._handleHandlers = function() {
+        this.$on.on("click.bootstrapSwitch", (function(_this) {
+          return function(event) {
+            event.preventDefault();
+            event.stopPropagation();
+            _this.state(false);
+            return _this.$element.trigger("focus.bootstrapSwitch");
+          };
+        })(this));
+        return this.$off.on("click.bootstrapSwitch", (function(_this) {
+          return function(event) {
+            event.preventDefault();
+            event.stopPropagation();
+            _this.state(true);
+            return _this.$element.trigger("focus.bootstrapSwitch");
+          };
+        })(this));
+      };
+
+      BootstrapSwitch.prototype._labelHandlers = function() {
+        return this.$label.on({
+          "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) {
+            return function(e) {
+              if (_this._dragStart || _this.options.disabled || _this.options.readonly) {
+                return;
+              }
+              e.preventDefault();
+              e.stopPropagation();
+              _this._dragStart = (e.pageX || e.originalEvent.touches[0].pageX) - parseInt(_this.$container.css("margin-left"), 10);
+              if (_this.options.animate) {
+                _this.$wrapper.removeClass("" + _this.options.baseClass + "-animate");
+              }
+              return _this.$element.trigger("focus.bootstrapSwitch");
+            };
+          })(this),
+          "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) {
+            return function(e) {
+              var difference;
+              if (_this._dragStart == null) {
+                return;
+              }
+              e.preventDefault();
+              difference = (e.pageX || e.originalEvent.touches[0].pageX) - _this._dragStart;
+              if (difference < -_this._handleWidth || difference > 0) {
+                return;
+              }
+              _this._dragEnd = difference;
+              return _this.$container.css("margin-left", "" + _this._dragEnd + "px");
+            };
+          })(this),
+          "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (function(_this) {
+            return function(e) {
+              var state;
+              if (!_this._dragStart) {
+                return;
+              }
+              e.preventDefault();
+              if (_this.options.animate) {
+                _this.$wrapper.addClass("" + _this.options.baseClass + "-animate");
+              }
+              if (_this._dragEnd) {
+                state = _this._dragEnd > -(_this._handleWidth / 2);
+                _this._dragEnd = false;
+                _this.state(_this.options.inverse ? !state : state);
+              } else {
+                _this.state(!_this.options.state);
+              }
+              return _this._dragStart = false;
+            };
+          })(this),
+          "mouseleave.bootstrapSwitch": (function(_this) {
+            return function(e) {
+              return _this.$label.trigger("mouseup.bootstrapSwitch");
+            };
+          })(this)
+        });
+      };
+
+      BootstrapSwitch.prototype._externalLabelHandler = function() {
+        var $externalLabel;
+        $externalLabel = this.$element.closest("label");
+        return $externalLabel.on("click", (function(_this) {
+          return function(event) {
+            event.preventDefault();
+            event.stopImmediatePropagation();
+            if (event.target === $externalLabel[0]) {
+              return _this.toggleState();
+            }
+          };
+        })(this));
+      };
+
+      BootstrapSwitch.prototype._formHandler = function() {
+        var $form;
+        $form = this.$element.closest("form");
+        if ($form.data("bootstrap-switch")) {
+          return;
+        }
+        return $form.on("reset.bootstrapSwitch", function() {
+          return window.setTimeout(function() {
+            return $form.find("input").filter(function() {
+              return $(this).data("bootstrap-switch");
+            }).each(function() {
+              return $(this).bootstrapSwitch("state", this.checked);
+            });
+          }, 1);
+        }).data("bootstrap-switch", true);
+      };
+
+      BootstrapSwitch.prototype._getClasses = function(classes) {
+        var c, cls, _i, _len;
+        if (!$.isArray(classes)) {
+          return ["" + this.options.baseClass + "-" + classes];
+        }
+        cls = [];
+        for (_i = 0, _len = classes.length; _i < _len; _i++) {
+          c = classes[_i];
+          cls.push("" + this.options.baseClass + "-" + c);
+        }
+        return cls;
+      };
+
+      return BootstrapSwitch;
+
+    })();
+    $.fn.bootstrapSwitch = function() {
+      var args, option, ret;
+      option = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+      ret = this;
+      this.each(function() {
+        var $this, data;
+        $this = $(this);
+        data = $this.data("bootstrap-switch");
+        if (!data) {
+          $this.data("bootstrap-switch", data = new BootstrapSwitch(this, option));
+        }
+        if (typeof option === "string") {
+          return ret = data[option].apply(data, args);
+        }
+      });
+      return ret;
+    };
+    $.fn.bootstrapSwitch.Constructor = BootstrapSwitch;
+    return $.fn.bootstrapSwitch.defaults = {
+      state: true,
+      size: null,
+      animate: true,
+      disabled: false,
+      readonly: false,
+      indeterminate: false,
+      inverse: false,
+      radioAllOff: false,
+      onColor: "primary",
+      offColor: "default",
+      onText: "ON",
+      offText: "OFF",
+      labelText: "&nbsp;",
+      handleWidth: "auto",
+      labelWidth: "auto",
+      baseClass: "bootstrap-switch",
+      wrapperClass: "wrapper",
+      onInit: function() {},
+      onSwitchChange: function() {}
+    };
+  })(window.jQuery, window);
+
+}).call(this);

+ 146 - 0
lib/bootstrap-timepicker.css

@@ -0,0 +1,146 @@
+/*!
+ * Timepicker Component for Twitter Bootstrap
+ *
+ * Copyright 2013 Joris de Wit
+ *
+ * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+.bootstrap-timepicker {
+  position: relative;
+}
+.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu {
+  left: auto;
+  right: 0;
+}
+.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before {
+  left: auto;
+  right: 12px;
+}
+.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after {
+  left: auto;
+  right: 13px;
+}
+.bootstrap-timepicker .add-on {
+  cursor: pointer;
+}
+.bootstrap-timepicker .add-on i {
+  display: inline-block;
+  width: 16px;
+  height: 16px;
+}
+.bootstrap-timepicker-widget.dropdown-menu {
+  padding: 4px;
+}
+.bootstrap-timepicker-widget.dropdown-menu.open {
+  display: inline-block;
+}
+.bootstrap-timepicker-widget.dropdown-menu:before {
+  border-bottom: 7px solid rgba(0, 0, 0, 0.2);
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  content: "";
+  display: inline-block;
+  position: absolute;
+}
+.bootstrap-timepicker-widget.dropdown-menu:after {
+  border-bottom: 6px solid #FFFFFF;
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  content: "";
+  display: inline-block;
+  position: absolute;
+}
+.bootstrap-timepicker-widget.timepicker-orient-left:before {
+  left: 6px;
+}
+.bootstrap-timepicker-widget.timepicker-orient-left:after {
+  left: 7px;
+}
+.bootstrap-timepicker-widget.timepicker-orient-right:before {
+  right: 6px;
+}
+.bootstrap-timepicker-widget.timepicker-orient-right:after {
+  right: 7px;
+}
+.bootstrap-timepicker-widget.timepicker-orient-top:before {
+  top: -7px;
+}
+.bootstrap-timepicker-widget.timepicker-orient-top:after {
+  top: -6px;
+}
+.bootstrap-timepicker-widget.timepicker-orient-bottom:before {
+  bottom: -7px;
+  border-bottom: 0;
+  border-top: 7px solid #999;
+}
+.bootstrap-timepicker-widget.timepicker-orient-bottom:after {
+  bottom: -6px;
+  border-bottom: 0;
+  border-top: 6px solid #ffffff;
+}
+.bootstrap-timepicker-widget a.btn,
+.bootstrap-timepicker-widget input {
+  border-radius: 4px;
+}
+.bootstrap-timepicker-widget table {
+  width: 100%;
+  margin: 0;
+}
+.bootstrap-timepicker-widget table td {
+  text-align: center;
+  height: 30px;
+  margin: 0;
+  padding: 2px;
+}
+.bootstrap-timepicker-widget table td:not(.separator) {
+  min-width: 30px;
+}
+.bootstrap-timepicker-widget table td span {
+  width: 100%;
+}
+.bootstrap-timepicker-widget table td a {
+  border: 1px transparent solid;
+  width: 100%;
+  display: inline-block;
+  margin: 0;
+  padding: 8px 0;
+  outline: 0;
+  color: #333;
+}
+.bootstrap-timepicker-widget table td a:hover {
+  text-decoration: none;
+  background-color: #eee;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  border-color: #ddd;
+}
+.bootstrap-timepicker-widget table td a i {
+  margin-top: 2px;
+  font-size: 18px;
+}
+.bootstrap-timepicker-widget table td input {
+  width: 25px;
+  margin: 0;
+  text-align: center;
+}
+.bootstrap-timepicker-widget .modal-content {
+  padding: 4px;
+}
+@media (min-width: 767px) {
+  .bootstrap-timepicker-widget.modal {
+    width: 200px;
+    margin-left: -100px;
+  }
+}
+@media (max-width: 767px) {
+  .bootstrap-timepicker {
+    width: 100%;
+  }
+  .bootstrap-timepicker .dropdown-menu {
+    width: 100%;
+  }
+}

文件差异内容过多而无法显示
+ 1097 - 0
lib/bootstrap-timepicker.js


+ 476 - 0
lib/bootstrap/css/bootstrap-theme.css

@@ -0,0 +1,476 @@
+/*!
+ * Bootstrap v3.3.4 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+.btn-default,
+.btn-primary,
+.btn-success,
+.btn-info,
+.btn-warning,
+.btn-danger {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+}
+.btn-default:active,
+.btn-primary:active,
+.btn-success:active,
+.btn-info:active,
+.btn-warning:active,
+.btn-danger:active,
+.btn-default.active,
+.btn-primary.active,
+.btn-success.active,
+.btn-info.active,
+.btn-warning.active,
+.btn-danger.active {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn-default .badge,
+.btn-primary .badge,
+.btn-success .badge,
+.btn-info .badge,
+.btn-warning .badge,
+.btn-danger .badge {
+  text-shadow: none;
+}
+.btn:active,
+.btn.active {
+  background-image: none;
+}
+.btn-default {
+  text-shadow: 0 1px 0 #fff;
+  background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+  background-image:      -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
+  background-image:         linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #dbdbdb;
+  border-color: #ccc;
+}
+.btn-default:hover,
+.btn-default:focus {
+  background-color: #e0e0e0;
+  background-position: 0 -15px;
+}
+.btn-default:active,
+.btn-default.active {
+  background-color: #e0e0e0;
+  border-color: #dbdbdb;
+}
+.btn-default.disabled,
+.btn-default:disabled,
+.btn-default[disabled] {
+  background-color: #e0e0e0;
+  background-image: none;
+}
+.btn-primary {
+  background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
+  background-image:      -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
+  background-image:         linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #245580;
+}
+.btn-primary:hover,
+.btn-primary:focus {
+  background-color: #265a88;
+  background-position: 0 -15px;
+}
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #265a88;
+  border-color: #245580;
+}
+.btn-primary.disabled,
+.btn-primary:disabled,
+.btn-primary[disabled] {
+  background-color: #265a88;
+  background-image: none;
+}
+.btn-success {
+  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
+  background-image:      -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
+  background-image:         linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #3e8f3e;
+}
+.btn-success:hover,
+.btn-success:focus {
+  background-color: #419641;
+  background-position: 0 -15px;
+}
+.btn-success:active,
+.btn-success.active {
+  background-color: #419641;
+  border-color: #3e8f3e;
+}
+.btn-success.disabled,
+.btn-success:disabled,
+.btn-success[disabled] {
+  background-color: #419641;
+  background-image: none;
+}
+.btn-info {
+  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+  background-image:      -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
+  background-image:         linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #28a4c9;
+}
+.btn-info:hover,
+.btn-info:focus {
+  background-color: #2aabd2;
+  background-position: 0 -15px;
+}
+.btn-info:active,
+.btn-info.active {
+  background-color: #2aabd2;
+  border-color: #28a4c9;
+}
+.btn-info.disabled,
+.btn-info:disabled,
+.btn-info[disabled] {
+  background-color: #2aabd2;
+  background-image: none;
+}
+.btn-warning {
+  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+  background-image:      -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
+  background-image:         linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #e38d13;
+}
+.btn-warning:hover,
+.btn-warning:focus {
+  background-color: #eb9316;
+  background-position: 0 -15px;
+}
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #eb9316;
+  border-color: #e38d13;
+}
+.btn-warning.disabled,
+.btn-warning:disabled,
+.btn-warning[disabled] {
+  background-color: #eb9316;
+  background-image: none;
+}
+.btn-danger {
+  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+  background-image:      -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
+  background-image:         linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #b92c28;
+}
+.btn-danger:hover,
+.btn-danger:focus {
+  background-color: #c12e2a;
+  background-position: 0 -15px;
+}
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #c12e2a;
+  border-color: #b92c28;
+}
+.btn-danger.disabled,
+.btn-danger:disabled,
+.btn-danger[disabled] {
+  background-color: #c12e2a;
+  background-image: none;
+}
+.thumbnail,
+.img-thumbnail {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  background-color: #e8e8e8;
+  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image:      -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
+  background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+  background-repeat: repeat-x;
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  background-color: #2e6da4;
+  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+  background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+  background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+  background-repeat: repeat-x;
+}
+.navbar-default {
+  background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
+  background-image:      -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
+  background-image:         linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .active > a {
+  background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
+  background-image:      -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
+  background-image:         linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
+  background-repeat: repeat-x;
+  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
+}
+.navbar-brand,
+.navbar-nav > li > a {
+  text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
+}
+.navbar-inverse {
+  background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
+  background-image:      -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
+  background-image:         linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+}
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .active > a {
+  background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
+  background-image:      -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
+  background-image:         linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
+  background-repeat: repeat-x;
+  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
+          box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
+}
+.navbar-inverse .navbar-brand,
+.navbar-inverse .navbar-nav > li > a {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
+}
+.navbar-static-top,
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  border-radius: 0;
+}
+@media (max-width: 767px) {
+  .navbar .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #fff;
+    background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+    background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+    background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+    background-repeat: repeat-x;
+  }
+}
+.alert {
+  text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
+}
+.alert-success {
+  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+  background-image:      -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
+  background-image:         linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #b2dba1;
+}
+.alert-info {
+  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+  background-image:      -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
+  background-image:         linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #9acfea;
+}
+.alert-warning {
+  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+  background-image:      -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
+  background-image:         linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #f5e79e;
+}
+.alert-danger {
+  background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+  background-image:      -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
+  background-image:         linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #dca7a7;
+}
+.progress {
+  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+  background-image:      -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
+  background-image:         linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar {
+  background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
+  background-image:      -o-linear-gradient(top, #337ab7 0%, #286090 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
+  background-image:         linear-gradient(to bottom, #337ab7 0%, #286090 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-success {
+  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+  background-image:      -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
+  background-image:         linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-info {
+  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+  background-image:      -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
+  background-image:         linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-warning {
+  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+  background-image:      -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
+  background-image:         linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-danger {
+  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+  background-image:      -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
+  background-image:         linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-striped {
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.list-group {
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  text-shadow: 0 -1px 0 #286090;
+  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
+  background-image:      -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
+  background-image:         linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #2b669a;
+}
+.list-group-item.active .badge,
+.list-group-item.active:hover .badge,
+.list-group-item.active:focus .badge {
+  text-shadow: none;
+}
+.panel {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
+}
+.panel-default > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image:      -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
+  background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-primary > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+  background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+  background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-success > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+  background-image:      -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
+  background-image:         linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-info > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+  background-image:      -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
+  background-image:         linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-warning > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+  background-image:      -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
+  background-image:         linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-danger > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+  background-image:      -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
+  background-image:         linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
+  background-repeat: repeat-x;
+}
+.well {
+  background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+  background-image:      -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
+  background-image:         linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #dcdcdc;
+  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
+          box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
+}
+/*# sourceMappingURL=bootstrap-theme.css.map */

文件差异内容过多而无法显示
+ 1 - 0
lib/bootstrap/css/bootstrap-theme.css.map


文件差异内容过多而无法显示
+ 5 - 0
lib/bootstrap/css/bootstrap-theme.min.css


文件差异内容过多而无法显示
+ 6584 - 0
lib/bootstrap/css/bootstrap.css


文件差异内容过多而无法显示
+ 1 - 0
lib/bootstrap/css/bootstrap.css.map


文件差异内容过多而无法显示
+ 5 - 0
lib/bootstrap/css/bootstrap.min.css


二进制
lib/bootstrap/fonts/glyphicons-halflings-regular.eot


文件差异内容过多而无法显示
+ 288 - 0
lib/bootstrap/fonts/glyphicons-halflings-regular.svg


二进制
lib/bootstrap/fonts/glyphicons-halflings-regular.ttf


二进制
lib/bootstrap/fonts/glyphicons-halflings-regular.woff


二进制
lib/bootstrap/fonts/glyphicons-halflings-regular.woff2


文件差异内容过多而无法显示
+ 2317 - 0
lib/bootstrap/js/bootstrap.js


文件差异内容过多而无法显示
+ 7 - 0
lib/bootstrap/js/bootstrap.min.js


+ 13 - 0
lib/bootstrap/js/npm.js

@@ -0,0 +1,13 @@
+// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
+require('../../js/transition.js')
+require('../../js/alert.js')
+require('../../js/button.js')
+require('../../js/carousel.js')
+require('../../js/collapse.js')
+require('../../js/dropdown.js')
+require('../../js/modal.js')
+require('../../js/tooltip.js')
+require('../../js/popover.js')
+require('../../js/scrollspy.js')
+require('../../js/tab.js')
+require('../../js/affix.js')

+ 61 - 0
lib/d3-gauge-simple.css

@@ -0,0 +1,61 @@
+.d3-gauge.simple .outer-circle {
+  fill         :  #ccc;
+  stroke       :  #000;
+  stroke-width :  0.5px;
+}
+
+.d3-gauge.simple .inner-circle {
+  fill         :  #fff;
+  stroke       :  #E0E0E0;
+  stroke-width :  2px;
+}
+
+.d3-gauge.simple .label {
+  fill      :  #333;
+  font-size :  10px;
+}
+
+.d3-gauge.simple .major-tick {
+  stroke       :  #333;
+  stroke-width :  2px;
+}
+
+.d3-gauge.simple .minor-tick {
+  stroke       :  #666;
+  stroke-width :  1px;
+}
+
+.d3-gauge.simple .major-tick-label {
+  fill         :  darkblue;
+  stroke-width :  2px;
+  font-size    :  12px;
+}
+
+.d3-gauge.simple .needle {
+  fill         :  #dc3912;
+  stroke       :  #c63310;
+  fill-opacity :  0.7;
+}
+
+.d3-gauge.simple .needle-container {
+  fill         :  #4684EE;
+  stroke       :  #666;
+  fill-opacity :  1;
+}
+
+.d3-gauge.simple .current-value {
+  fill         :  #000;
+  stroke-width :  0px;
+}
+
+.d3-gauge.simple .green-zone {
+  fill :  #FF9900
+}
+
+.d3-gauge.simple .yellow-zone {
+  fill :  #FF9900
+}
+
+.d3-gauge.simple .red-zone {
+  fill :  #DC3912
+}

+ 407 - 0
lib/d3-gauge.js

@@ -0,0 +1,407 @@
+'use strict';
+
+// heavily inspired by: http://bl.ocks.org/tomerd/1499279
+
+// var xtend = require('xtend');
+
+function xtend() {
+    var target = {}
+
+    for (var i = 0; i < arguments.length; i++) {
+        var source = arguments[i]
+
+        for (var key in source) {
+            if (source.hasOwnProperty(key)) {
+                target[key] = source[key]
+            }
+        }
+    }
+
+    return target
+}
+
+// var defaultOpts = require('./defaults/simple');
+var defaultOpts = {
+    size :  125
+  , min  :  0
+  , max  :  100 
+  , transitionDuration : 500
+  , clazz : 'simple'
+
+  , label                      :  'label.text'
+  , minorTicks                 :  4
+  , majorTicks                 :  5
+  , needleWidthRatio           :  0.6
+  , needleContainerRadiusRatio :  0.7
+
+  , zones: [
+      { clazz: 'yellow-zone', from: 0.73, to: 0.9 }
+    , { clazz: 'red-zone', from: 0.9, to: 1.0 }
+    ]
+};
+
+// var d3 = require('d3');
+
+// var go = module.exports = Gauge;
+//var go = Gauge;
+var proto = Gauge.prototype;
+
+/**
+ * Creates a gauge appended to the given DOM element.
+ *
+ * Example: 
+ *
+ * ```js
+ *  var simpleOpts = {
+ *      size :  100
+ *    , min  :  0
+ *    , max  :  50 
+ *    , transitionDuration : 500
+ *
+ *    , label                      :  'label.text'
+ *    , minorTicks                 :  4
+ *    , majorTicks                 :  5
+ *    , needleWidthRatio           :  0.6
+ *    , needleContainerRadiusRatio :  0.7
+ *
+ *    , zones: [
+ *        { clazz: 'yellow-zone', from: 0.73, to: 0.9 }
+ *      , { clazz: 'red-zone', from: 0.9, to: 1.0 }
+ *      ]
+ *  }
+ *  var gauge = Gauge(document.getElementById('simple-gauge'), simpleOpts);
+ *  gauge.write(39);
+ * ```
+ * 
+ * @name Gauge
+ * @function
+ * @param el {DOMElement} to which the gauge is appended
+ * @param opts {Object} gauge configuration with the following properties all of which have sensible defaults:
+ *  - label {String} that appears in the top portion of the gauge
+ *  - clazz {String} class to apply to the gauge element in order to support custom styling
+ *  - size {Number} the over all size (radius) of the gauge
+ *  - preserveAspectRatio {String} default 'xMinYMin meet', see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio
+ *  - min {Number} the minimum value that the gauge measures
+ *  - max {Number} the maximum value that the gauge measures
+ *  - majorTicks {Number} the number of major ticks to draw 
+ *  - minorTicks {Number} the number of minor ticks to draw in between two major ticks
+ *  - needleWidthRatio {Number} tweaks the gauge's needle width
+ *  - needleConatinerRadiusRatio {Number} tweaks the gauge's needle container circumference
+ *  - transitionDuration {Number} the time in ms it takes for the needle to move to a new position
+ *  - zones {Array[Object]} each with the following properties
+ *    - clazz {String} class to apply to the zone element in order to style its fill
+ *    - from {Number} between 0 and 1 to determine zone's start 
+ *    - to {Number} between 0 and 1 to determine zone's end 
+ * @return {Object} the gauge with a `write` method
+ */
+
+ function Gauge() {
+
+ }
+
+Gauge.prototype.draw = function (el, opts) {
+  if (!(this instanceof Gauge)) return new Gauge(el, opts);
+
+  this._el = el;
+
+  this._opts = xtend(defaultOpts, opts);  
+
+  this._size   =  this._opts.size;
+  this._radius =  this._size * 0.9 / 2;
+
+  this._cx     =  this._size / 2;
+  this._cy     =  this._cx;
+
+  this._preserveAspectRatio = this._opts.preserveAspectRatio;
+
+  this._min    =  this._opts.min;
+  this._max    =  this._opts.max;
+  this._range  =  this._max - this._min;
+
+  this._majorTicks = this._opts.majorTicks;
+  this._minorTicks = this._opts.minorTicks;
+
+  this._needleWidthRatio = this._opts.needleWidthRatio;
+  this._needleContainerRadiusRatio = this._opts.needleContainerRadiusRatio;
+  
+  this._transitionDuration = this._opts.transitionDuration;
+  this._label = this._opts.label;
+
+  this._zones = this._opts.zones || [];
+
+  this._clazz = this._opts.clazz;
+
+  this._initZones();
+  this._render();
+}
+
+/**
+ * Writes a value to the gauge and updates its state, i.e. needle position, accordingly.
+ * @name write
+ * @function
+ * @param value {Number} the new gauge value, should be in between min and max
+ * @param transitionDuration {Number} (optional) transition duration, if not supplied the configured duration is used
+ */
+proto.write = function(value, transitionDuration) {
+  var self = this;
+
+  function transition () {
+    var needleValue = value
+      , overflow = value > self._max
+      , underflow = value < self._min;
+
+         if (overflow)  needleValue = self._max + 0.02 * self._range;
+    else if (underflow) needleValue = self._min - 0.02 * self._range;
+
+    var targetRotation = self._toDegrees(needleValue) - 90
+      , currentRotation = self._currentRotation || targetRotation;
+
+    self._currentRotation = targetRotation;
+    
+    return function (step) {
+      var rotation = currentRotation + (targetRotation - currentRotation) * step;
+      return 'translate(' + self._cx + ', ' + self._cy + ') rotate(' + rotation + ')'; 
+    }
+  }
+
+  var needleContainer = this._gauge.select('.needle-container');
+  
+  needleContainer
+    .selectAll('text')
+    .attr('class', 'current-value')
+    .text(Math.round(value));
+  
+  var needle = needleContainer.selectAll('path');
+  needle
+    .transition()
+    .duration(transitionDuration ? transitionDuration : this._transitionDuration)
+    .attrTween('transform', transition);
+}
+
+proto._initZones = function () {
+  var self = this;
+
+  function percentToVal (percent) {
+    return self._min + self._range * percent;
+  }
+
+  function initZone (zone) {
+    return { 
+        clazz: zone.clazz
+      , from: percentToVal(zone.from)
+      , to:  percentToVal(zone.to)
+    }
+  }
+
+  // create new zones to not mess with the passed in args
+  this._zones = this._zones.map(initZone);
+}
+
+proto._render = function () {
+  this._initGauge();
+  this._drawOuterCircle();
+  this._drawInnerCircle();
+  this._drawLabel();
+
+  this._drawZones();
+  this._drawTicks();
+
+  this._drawNeedle();
+  this.write(this._min, 0);
+}
+
+proto._initGauge = function () {
+  this._gauge = d3.select(this._el)
+    .append('svg:svg')
+    .attr('class'  ,  'd3-gauge' + (this._clazz ? ' ' + this._clazz : ''))
+    .attr('width'  ,  this._size)
+    .attr('height' ,  this._size)
+    .attr('viewBox',  '0 0 ' + this._size + ' ' + this._size)
+    .attr('preserveAspectRatio', this._preserveAspectRatio || 'xMinYMin meet')
+}
+
+proto._drawOuterCircle = function () {
+  this._gauge
+    .append('svg:circle')
+    .attr('class' ,  'outer-circle')
+    .attr('cx'    ,  this._cx)
+    .attr('cy'    ,  this._cy)
+    .attr('r'     ,  this._radius)
+}
+
+proto._drawInnerCircle = function () {
+  this._gauge
+    .append('svg:circle')
+    .attr('class' ,  'inner-circle')
+    .attr('cx'    ,  this._cx)
+    .attr('cy'    ,  this._cy)
+    .attr('r'     ,  0.9 * this._radius)
+}
+
+proto._drawLabel = function () {
+  if (typeof this._label === undefined) return;
+
+  var fontSize = Math.round(this._size / 9);
+  var halfFontSize = fontSize / 2;
+
+  this._gauge
+    .append('svg:text')
+    .attr('class', 'label')
+    .attr('x', this._cx)
+    .attr('y', this._cy / 2 + halfFontSize)
+    .attr('dy', halfFontSize)
+    .attr('text-anchor', 'middle')
+    .text(this._label)
+}
+
+proto._drawTicks = function () {
+  var majorDelta = this._range / (this._majorTicks - 1)
+    , minorDelta = majorDelta / this._minorTicks
+    , point 
+    ;
+
+  for (var major = this._min; major <= this._max; major += majorDelta) {
+    var minorMax = Math.min(major + majorDelta, this._max);
+    for (var minor = major + minorDelta; minor < minorMax; minor += minorDelta) {
+      this._drawLine(this._toPoint(minor, 0.75), this._toPoint(minor, 0.85), 'minor-tick');
+    }
+
+    this._drawLine(this._toPoint(major, 0.7), this._toPoint(major, 0.85), 'major-tick');
+
+    if (major === this._min || major === this._max) {
+      point = this._toPoint(major, 0.63);
+      this._gauge
+        .append('svg:text')
+        .attr('class', 'major-tick-label')
+        .attr('x', point.x)
+        .attr('y', point.y)
+        .attr('text-anchor', major === this._min ? 'start' : 'end')
+        .text(major)
+    }
+  }
+}
+
+proto._drawLine = function (p1, p2, clazz) {
+  this._gauge
+    .append('svg:line')
+    .attr('class' ,  clazz)
+    .attr('x1'    ,  p1.x)
+    .attr('y1'    ,  p1.y)
+    .attr('x2'    ,  p2.x)
+    .attr('y2'    ,  p2.y)
+}
+
+proto._drawZones = function () {
+  var self = this;
+  function drawZone (zone) {
+    self._drawBand(zone.from, zone.to, zone.clazz);
+  }
+
+  this._zones.forEach(drawZone);
+}
+
+proto._drawBand = function (start, end, clazz) {
+  var self = this;
+
+  function transform () {
+    return 'translate(' + self._cx + ', ' + self._cy +') rotate(270)';
+  }
+
+  var arc = d3.svg.arc()
+    .startAngle(this._toRadians(start))
+    .endAngle(this._toRadians(end))
+    .innerRadius(0.65 * this._radius)
+    .outerRadius(0.85 * this._radius)
+    ;
+
+  this._gauge
+    .append('svg:path')
+    .attr('class', clazz)
+    .attr('d', arc)
+    .attr('transform', transform)
+}
+
+proto._drawNeedle = function () {
+
+  var needleContainer = this._gauge
+    .append('svg:g')
+    .attr('class', 'needle-container');
+		
+  var midValue = (this._min + this._max) / 2;
+  
+  var needlePath = this._buildNeedlePath(midValue);
+  
+  var needleLine = d3.svg.line()
+      .x(function(d) { return d.x })
+      .y(function(d) { return d.y })
+      .interpolate('basis');
+  
+  needleContainer
+    .selectAll('path')
+    .data([ needlePath ])
+    .enter()
+      .append('svg:path')
+        .attr('class' ,  'needle')
+        .attr('d'     ,  needleLine)
+        
+  needleContainer
+    .append('svg:circle')
+    .attr('cx'            ,  this._cx)
+    .attr('cy'            ,  this._cy)
+    .attr('r'             ,  this._radius * this._needleContainerRadiusRatio / 10)
+
+  // TODO: not styling font-size since we need to calculate other values from it
+  //       how do I extract style value?
+  var fontSize = Math.round(this._size / 10);
+  needleContainer
+    .selectAll('text')
+    .data([ midValue ])
+    .enter()
+      .append('svg:text')
+        .attr('x'             ,  this._cx)
+        .attr('y'             ,  this._size - this._cy / 4 - fontSize)
+        .attr('dy'            ,  fontSize / 2)
+        .attr('text-anchor'   ,  'middle')
+}
+
+proto._buildNeedlePath = function (value) {
+  var self = this;
+
+  function valueToPoint(value, factor) {
+    var point = self._toPoint(value, factor);
+    point.x -= self._cx;
+    point.y -= self._cy;
+    return point;
+  }
+
+  var delta = this._range * this._needleWidthRatio / 10
+    , tailValue = value - (this._range * (1/ (270/360)) / 2)
+
+  var head = valueToPoint(value, 0.85)
+    , head1 = valueToPoint(value - delta, 0.12)
+    , head2 = valueToPoint(value + delta, 0.12)
+  
+  var tail = valueToPoint(tailValue, 0.28)
+    , tail1 = valueToPoint(tailValue - delta, 0.12)
+    , tail2 = valueToPoint(tailValue + delta, 0.12)
+  
+  return [head, head1, tail2, tail, tail1, head2, head];
+}
+
+proto._toDegrees = function (value) {
+  // Note: tried to factor out 'this._range * 270' but that breaks things, most likely due to rounding behavior
+  return value / this._range * 270 - (this._min / this._range * 270 + 45);
+}
+
+proto._toRadians = function (value) {
+  return this._toDegrees(value) * Math.PI / 180;
+}
+
+proto._toPoint = function (value, factor) {
+  var len = this._radius * factor;
+  var inRadians = this._toRadians(value);
+  return {
+    x: this._cx - len * Math.cos(inRadians),
+    y: this._cy - len * Math.sin(inRadians)
+  };
+}

文件差异内容过多而无法显示
+ 5 - 0
lib/d3.v3.min.js


文件差异内容过多而无法显示
+ 4 - 0
lib/jquery-1.11.2.min.js


+ 24 - 0
manifest.json

@@ -0,0 +1,24 @@
+{
+  "manifest_version": 2,
+  "version": "0.2.1",
+  "name": "meBarista",
+  "default_locale": "en",
+  "description": "meBarista for the meCoffee espresso machine controller. The easiest and most complete espresso PID.",
+  "icons": { "16": "images/logo-16.png", 
+             "32": "images/logo-32.png", 
+             "64": "images/logo-64.png", 
+             "96": "images/logo-96.png", 
+             "512": "images/logo-512.png" },
+
+  "app": {
+    "background": {
+      "scripts": ["background.js"]
+    }
+  },
+  
+  "bluetooth": {
+    "profiles": [ "00001101-0000-1000-8000-00805F9B34FB" ],
+    "uuids": [ "1105", "1106", "00001101-0000-1000-8000-00805F9B34FB" ],
+    "socket": true
+  }
+}

+ 555 - 0
mecoffee.js

@@ -0,0 +1,555 @@
+/* 
+
+    Bluetooth 
+
+*/
+function ab2str(buf) {
+  return String.fromCharCode.apply(null, new Uint8Array(buf));
+}
+
+function str2ab(str) {
+  var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
+  var bufView = new Uint16Array(buf);
+  for (var i=0, strLen=str.length; i<strLen; i++) {
+    bufView[i] = str.charCodeAt(i);
+  }
+  return buf;
+}
+
+var receive_buffer = "";
+var received_log = "";
+var disconnect_timer;
+var legacy = true;
+
+var slider_pistrt ;
+
+function onReceiveTimeout() {
+
+  //mecoffee_disconnect();
+  mecoffee_start();
+
+  disconnect_timer = null;
+
+}
+
+var onReceive = function(receiveInfo) {
+  receive_buffer += ab2str( receiveInfo.data );
+
+  var pos = 0, p = 0, i =0 ;
+  while( ( p = receive_buffer.indexOf("\n") ) > 0 ) {
+  	line =  receive_buffer.substring( 0, p - 1 );
+
+    // console.log( line );
+    receive_buffer = receive_buffer.substring( p + 1, receive_buffer.length );
+
+    // Keep all received lines in a variable
+    // received_log.concat( line + "\r\n" );
+
+    mecoffee_terminal( line );
+    i++;
+    if( i > 25 )
+    	break;
+  }
+
+  // Disconnect timer
+  if( disconnect_timer )
+    clearTimeout( disconnect_timer );
+
+  disconnect_timer = setTimeout( onReceiveTimeout, 5000 );
+};
+
+var onConnectedCallback = function() {
+  if (chrome.runtime.lastError) 
+    console.log("Connection failed: " + chrome.runtime.lastError.message); 
+  //else
+    //chrome.bluetoothSocket.onReceive.addListener( onReceive );
+};
+
+chrome.bluetoothSocket.onReceive.addListener( onReceive );
+
+var mecoffee_socketId;
+
+function mecoffee_connect( device_name ) {
+	var device;
+
+	chrome.bluetooth.getDevices(function(devices) {
+  
+		  for (var i = 0; i < devices.length; i++) {
+
+		  	if( devices[i].name == device_name ) {
+		    console.log(devices[i].name);
+		    device = devices[i];
+
+		    	var uuid = '1106';
+		    	uuid= "00001101-0000-1000-8000-00805F9B34FB";
+
+				chrome.bluetoothSocket.create( function(createInfo) {
+          mecoffee_socketId = createInfo.socketId;
+				  chrome.bluetoothSocket.connect(createInfo.socketId,
+				    device.address, uuid, onConnectedCallback);
+
+          chrome.bluetoothSocket.onAcceptError.addListener( function() { console.log('onaccepterror'); } );
+          chrome.bluetoothSocket.onReceiveError.addListener( function() { console.log('onreceiveerror'); } );
+
+          chrome.runtime.sendMessage( { type: 'socket', socket:  mecoffee_socketId }, function(response) { console.log( 'socket antwoord' ); } );
+
+				});
+
+
+		    break;
+		  }
+
+		}
+	});
+
+}
+
+function mecoffee_disconnect( ) {
+  chrome.bluetoothSocket.disconnect( mecoffee_socketId );
+  chrome.bluetoothSocket.close( mecoffee_socketId );
+
+  mecoffee_timestamp_start = null;
+}
+
+function mecoffee_bt_enum_devices( ) {
+  if( !chrome || !chrome.bluetooth )
+    return ;
+
+  chrome.bluetooth.getDevices(function(devices) {
+  
+    $('#device option[value="none"]').remove()      
+
+    for (var i = 0; i < devices.length; i++) {
+      $('#devices').append('<option value="device_' + devices[i].name + '">' + devices[i].name + '</option>');
+    }
+
+    $('#device')[0].selectedIndex = 0;
+    $('#device').selectpicker("refresh")
+
+  });
+
+}
+
+/* 
+    meCoffee protocol 
+*/
+
+var mecoffee_timestamp_start;
+function mecoffee_terminal_tmp( items ) {
+	var ts = parseInt( items[1] );
+	var tmp = parseInt( items[3] ) / 100;
+  var tmp2 = parseInt( items[4] ) / 100;
+	var tmpsp = parseInt( items[2] ) / 100;
+
+  if( !mecoffee_timestamp_start && mecoffee_timestamp_start != 0 ) {
+    mecoffee_timestamp_start = ts ;
+
+    mecoffee_terminal_write( "\r\ncmd dump OK\r\n" );
+  }
+
+  if( ts - mecoffee_timestamp_start == 2 ) {
+    var d = new Date(), e = new Date(d);
+    var msSinceMidnight = e - d.setHours(0,0,0,0);
+
+    mecoffee_terminal_write( "\r\ncmd clock set " + parseInt( msSinceMidnight / 1000 ) + " OK\r\n" );
+
+    console.log( 'clock set to ' + parseInt( msSinceMidnight / 1000 ) );
+  }
+
+  if( ts - mecoffee_timestamp_start == 5 ) {
+   
+    mecoffee_terminal_write( "\r\ncmd uname OK\r\n");
+  
+  }
+  
+  graph.tick( tmp, tmp2, tmpsp, ts - mecoffee_timestamp_start );
+
+	//mecoffee_cur_tmp = tmp;
+	//mecoffee_cur_sp = tmpsp;
+
+	document.querySelector('#mc_tmp').value = tmp;
+	document.querySelector('#mc_tmpsp').value = tmpsp;
+}
+
+var gauge_boiler;
+function mecoffee_terminal_pid( items ) {
+  var pid = parseInt( items[1] ) + parseInt( items[2] ) + parseInt( items[3] );
+  gauge_boiler.write( pid / 65536 * 100);
+}
+
+var gauge_shottimer;
+var gauge_shottimer_time;
+var gauge_shottimer_timeout;
+var gauge_shottimer_interval = null;
+function mecoffee_terminal_shot( items ) {
+  var value = parseInt( items[ items.length - 2 ] );
+  var speed = 1;
+
+  if( mecoffee_demo_interval > 0 ) {
+    speed = $('#demo_speed')[0].value;
+  }
+
+  if( gauge_shottimer_timeout ) {
+    clearTimeout( gauge_shottimer_timeout );
+    gauge_shottimer_timeout = null;
+  }
+
+  $('#gauge_shottimer').show();
+
+  // start interval to update shot timer every sec 
+  if( value == 0 && gauge_shottimer_interval == null ) {
+    gauge_shottimer_time = 0;
+    console.log( 'started interval ' );
+    gauge_shottimer_interval = setInterval( function() {
+      gauge_shottimer.write( ++gauge_shottimer_time );
+      //console.log( 'update shot timer to ' + gauge_shottimer_time );
+    },
+    1000 / speed );
+  }
+
+  // clean up shot timer after 30 secs
+  if( value > 0 ) {
+    if( gauge_shottimer_interval ) {
+      clearInterval( gauge_shottimer_interval );
+      gauge_shottimer_interval = null;
+      gauge_shottimer.write( value / 1000.0 );
+    }
+
+
+
+    gauge_shottimer_timeout = setTimeout( function() {                         
+      gauge_shottimer.write( 0 );
+      $('#gauge_shottimer').hide(); 
+      gauge_shottimer_timeout = null; 
+
+    },
+    Math.min( 30000, 30000 / speed )                                
+  );
+
+  }
+
+  gauge_shottimer.write( value / 1000 );
+}
+
+
+function mecoffee_terminal_cmd( items ) {
+  
+  if( items[1] == 'get' ) {
+    var elems = $( '#mc_' + items[2] );
+    var value = items[3];
+
+    
+
+
+    if( elems.length == 0 ) {
+      console.log( '#mc_' + items[2]  + ' not found' );
+
+      return;
+    }
+
+    var elem = elems[0];
+    var scale = elem.getAttribute('data-scale');
+
+    if( scale )
+      value /= scale;
+
+    if( elems[0].className.indexOf('timepicker') >= 0) {
+      var hour = Math.floor( value / ( 60*60 ) );
+      var min = value % ( 60*60 );
+
+      elems[0].value = hour + ':' + Math.floor( min / 60 );
+
+      return;
+    }
+
+    if( elems[0].type == "checkbox" ) {
+      elems.bootstrapSwitch('state' , items[3] == "1" );
+      return;
+    }
+
+    if( elems.attr( 'data-slider-value' ) ) {
+      elems.slider( 'setValue', parseInt( items[3] ) );
+      return;
+    }
+
+    elems[0].value = value;
+  }
+}
+
+function mecoffee_terminal_uname( line ) {
+  
+  legacy = line.indexOf( 'V4' ) > 0;
+
+  if( legacy ) {
+    console.log( 'meCoffee is legacy ( V4 )' );
+
+    $('#mc_pistrt')[0].setAttribute( 'data-scale', 1 );
+    $('#mc_pistrt')[0].setAttribute( 'data-slider-step', 1 );
+
+    $('#mc_pistrt').slider( 'destroy' );
+    $('#mc_pistrt').slider( );
+
+    $( '#mc_pistrt' ).change( mecoffee_control_change );
+    
+    $('#mc_piprd')[0].setAttribute( 'data-scale', 1 );
+    $('#mc_piprd')[0].setAttribute( 'data-slider-step', 1 );
+
+    $('#mc_piprd').slider( 'destroy' );
+    $('#mc_piprd').slider( );
+
+    $( '#mc_piprd' ).change( mecoffee_control_change );
+
+  }
+  else {
+
+    console.log( 'meCoffee has newish firmware' );
+  
+    $('#mc_pistrt')[0].setAttribute( 'data-scale', 1000 );
+    $('#mc_pistrt')[0].setAttribute( 'data-slider-step', "0.1" );
+
+    $('#mc_pistrt').slider( 'destroy' );
+    $('#mc_pistrt').slider( );
+
+    $( '#mc_pistrt' ).change( mecoffee_control_change );
+
+    $('#mc_piprd')[0].setAttribute( 'data-scale', 1000 );
+    $('#mc_piprd')[0].setAttribute( 'data-slider-step', 0.1 );
+
+    $('#mc_piprd').slider( 'destroy' );
+    $('#mc_piprd').slider( );
+
+    $( '#mc_piprd' ).change( mecoffee_control_change );
+
+  }
+
+
+  // $('.slider').destroy();
+
+  // $('.slider').slider();
+
+  // $('#mc_pistrt')[0].setAttribute( 'data-scala', 1000 );
+
+}
+
+function mecoffee_terminal( line ) {
+  received_log = received_log.concat( line + "\n" );
+    
+  var items = line.split(' ');
+
+  if( items[ items.length - 1 ] != 'OK' )
+    return;
+
+  if( items[0] == 'tmp' ) {
+  	mecoffee_terminal_tmp( items );
+  }
+
+  if( items[1] == 'uname' ) {
+    mecoffee_terminal_uname( line );
+    return;
+  }
+
+
+  if( items[0] == 'cmd' )
+    mecoffee_terminal_cmd( items );
+
+  if( items[0] == 'pid' )
+    mecoffee_terminal_pid( items );
+
+  if( items[0] == 'sht' )
+    mecoffee_terminal_shot( items );
+
+}
+
+function mecoffee_terminal_write( line ) {
+  if( !mecoffee_socketId )
+    return ;
+
+  chrome.bluetoothSocket.send( mecoffee_socketId, str2ab( line ), function(bytes_sent) {
+  
+  if (chrome.runtime.lastError) {
+    console.log("Send failed: " + chrome.runtime.lastError.message);
+  } else {
+    console.log("Sent " + bytes_sent + " bytes")
+  }
+  });
+}
+
+
+/* 
+
+    Demo 
+
+*/
+var mecoffee_demo_log = "";
+var mecoffee_demo_interval = 0;
+var mecoffee_demo_cnt = 0;
+
+function mecoffee_demo_timer( ) {
+  if( mecoffee_demo_cnt >= mecoffee_demo_log.length ) {
+    
+    mecoffee_demo_stop();
+
+    //clearInterval( mecoffee_demo_interval );
+    // TODO: change start button state
+    
+    //mecoffee_demo_cnt = 0;
+  }
+
+  for( ; mecoffee_demo_cnt < mecoffee_demo_log.length ; mecoffee_demo_cnt++ ) {
+    var line = mecoffee_demo_log[mecoffee_demo_cnt];
+    mecoffee_terminal( line );
+
+    if( line.indexOf('tmp ') == 0 ) {
+      mecoffee_demo_cnt++;
+      break;
+    }
+  }
+}
+
+function mecoffee_demo( log ) {
+  log = log.replace( "share_", "" ); 
+
+  $.get( "https://mecoffee.nl/share/logs/" + log, function( data ) {
+    mecoffee_demo_log = data.split("\n");
+    
+    mecoffee_demo_interval = setInterval( mecoffee_demo_timer, 1000 / $('#demo_speed')[0].value );
+  });
+ 
+  return false;
+}
+
+function mecoffee_demo_speed( e ) {
+  if( !mecoffee_demo_interval )
+    return ;
+
+  clearInterval( mecoffee_demo_interval );
+  mecoffee_demo_interval = setInterval( mecoffee_demo_timer, 1000 / e.target.value  );
+}
+
+function mecoffee_demo_stop( e ) {
+  if( mecoffee_demo_interval )
+    clearInterval( mecoffee_demo_interval );
+  mecoffee_demo_interval = 0;
+
+  mecoffee_demo_cnt = 0;
+
+   $('#start_icon')[0].className = "glyphicon glyphicon-play";
+  $('#start_label')[0].innerHTML = "Start";
+  start.className ="btn btn-primary";
+
+}
+
+
+/*
+
+    App  
+
+*/
+
+function mecoffee_start( e ) {
+  var start = $('#start')[0];
+
+  if( start.className.indexOf("danger") > 0 ) {
+    // Stop
+    start.className ="btn btn-primary";
+    $('#start_icon')[0].className = "glyphicon glyphicon-play";
+    $('#start_label')[0].innerHTML = "Start";
+
+    mecoffee_disconnect( );
+    mecoffee_demo_stop( );
+  }
+  else {
+    // Start
+    start.className ="btn btn-danger";
+    $('#start_icon')[0].className = "glyphicon glyphicon-stop";
+    $('#start_label')[0].innerHTML = "Stop";
+
+    var value = $('#device')[0].value;
+
+    if( value.indexOf('device_') == 0 )
+      mecoffee_connect( value.replace( 'device_', '' ) );
+
+    if( value.indexOf('share_') == 0 )
+      mecoffee_demo( value );
+  } 
+
+  return false;
+}
+
+function mecoffee_share( e ) {
+  $.post( "https://mecoffee.nl/share/post.php", { logfile: received_log }, function( data ) {
+  $( "#share_result" ).html( '<a target="_blank" href=""' + data + '">' + data + '</a>');
+  window.open( data, "_blank" );
+});
+
+
+  return false;
+}
+
+function mecoffee_control_change( e ) {
+  var elem = e.target;
+
+  if( elem.id.indexOf('mc_') != 0 )
+    return;
+
+  var value = elem.value;
+
+  if( e.value && e.value.newValue )
+    value = e.value.newValue;
+
+  if( elem.type == "checkbox" )
+    value = elem.checked ? 1 : 0;
+
+  if( value.indexOf && value.indexOf(':') > 0 ) {
+    var p = value.split(':');
+
+    value = 60*60*p[0] + 60*p[1];
+  }
+
+  var scale = elem.getAttribute('data-scale');
+
+  if( scale )
+    value *= scale;
+  
+  var line = "\r\ncmd set " + elem.id.replace( "mc_", "" ) + " " + value + " OK\r\n";
+  
+  console.log( line );
+  mecoffee_terminal_write( line );
+}
+
+var graph;  
+function mecoffee_app_startup() {
+
+  //document.querySelector("#doit").addEventListener( 'click', doit_doit );
+
+  $('#start').click( mecoffee_start );
+  $('#share').click( mecoffee_share );
+
+  window.addEventListener("resize",  function() { graph.draw(); });
+
+  graph = new meCoffeeGraph(); graph.draw();
+
+  $("input[type='checkbox']").bootstrapSwitch( { onSwitchChange: function ( e ) { mecoffee_control_change( e ); }});
+
+  // slider_pistrt = new Slider( '#mc_pistrt' );
+
+  $('.slider').slider();
+  $('.timepicker').timepicker( { showMeridian: false } );
+
+  $( ".target" ).change( mecoffee_control_change );
+  $( "#demo_speed").change( mecoffee_demo_speed );
+
+  //var 
+  gauge_boiler = new Gauge();
+  gauge_boiler.draw( $('#gauge_boiler')[0] , { label: 'Boiler %' } );
+
+  gauge_shottimer = new Gauge();
+  gauge_shottimer.draw( $('#gauge_shottimer')[0] , { label: 'Shot (s)', max: 30 } );
+
+
+  mecoffee_bt_enum_devices( );
+
+}
+
+window.addEventListener("DOMContentLoaded", mecoffee_app_startup );
+