diff --git a/README.md b/README.md index 76953a0..e816400 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. If you would like to include this code in a project which is not licensed under the AGPL V3, please contact the author at andrewhodel@gmail.com +## additional credits +This repo is a mixture of the above repo, and work done by bryanchanrobot +https://github.com/wgbartley/grblweb +https://github.com/brychanrobot/grblweb + ## Raspberry Pi prebuilt Image There is a prebuilt Raspberry Pi Image that is fully configured. More information and a link to the .img can be found at http://xyzbots.com @@ -35,6 +40,12 @@ https://github.com/grbl/grbl/wiki/Configuring-Grbl-v0.8 http://onehossshay.wordpress.com/2011/08/21/grbl-how-it-works-and-other-thoughts/ ## Installation +node --version +v0.10.32 +npm --version +1.4.28 + +I ran into errors with a base install of nodejs on ubuntu ``` git clone https://github.com/andrewhodel/grblweb.git @@ -58,6 +69,9 @@ node server.js npm install -g forever forever start server.js ``` +## Camera +you'll presently need to install mjpg-streamer, working on something more integrated, but no promises +https://github.com/jacksonliam/mjpg-streamer ## Access @@ -72,3 +86,13 @@ Read http://www.hobbytronics.co.uk/raspberry-pi-serial-port Set config.usettyAMA0 to 1 in config.js This is already enabled on the prebuilt GRBLWeb Raspbian image. + +## Fixes +script is looking for mjpg port again +camera viewport readded to main screen +minjs stuff is included again (have to check to see if that's actually "ok" or if I need to point to downloads there as well) +USB serial port is available for selection + +## dev setup +raspi V1 - raspi camera - linksprite 3 axis CNC +http://linksprite.com/wiki/index.php5?title=DIY_CNC_3_Axis_Engraver_Machine_PCB_Milling_Wood_Carving_Router_Kit_Arduino_Grbl diff --git a/config.js b/config.js index 05922db..6954357 100644 --- a/config.js +++ b/config.js @@ -1,12 +1,57 @@ +// !!! All values that are distance measures should be entered in millimeters !!! var config = {}; -config.webPort = 8000; +// Port to listen on for web site +config.webPort = 80; -config.serialBaudRate = 9600; +// Serial baud rate to Arduino +config.serialBaudRate = 115200; +// Use /dev/ttyAMA0 config.usettyAMA0 = 0; +// Enable simple jog controls +config.enableSimpleControls = 1; + +// Enable probe controls +config.enableProbeControls = 1; + +// Enable jsCut +config.enableJsCut = true; + +// Default step increment for jogging +config.jogControlDefaultIncr = 0.1; + +// Default feed rate for jogging +config.jogControlDefaultFeed = 1000; + +// X offset for probe +config.probeControlXOffset = -0.5; + +// Y offset for probe +config.probeControlYOffset = -1.5; + +// Z offset for probe +config.probeControlZOffset = 3.1; + +// Maximum you can move Z with any 1 command +config.maxMoveZ = 50; + +// Auto-read temperature +config.enablePiTemperature = false; +config.piTemperatureFahrenheit = true; +config.piTemperatureFile = [ + '/sys/class/thermal/thermal_zone0/temp', + '/sys/bus/w1/devices/28-000002aa87dd/w1_slave' +]; + +config.showUnitsBackdrop = false; + +// TO DO: +// - Add probe diameters +// - Allow configuration of gcode for probing buttons + // expects a webcam stream from mjpg_streamer config.webcamPort = 8080; diff --git a/i/index.html b/i/index.html index 5f0aa64..6040200 100644 --- a/i/index.html +++ b/i/index.html @@ -1,3 +1,9 @@ + + - + @@ -40,38 +46,33 @@ + - - - - - - - + + + + + + + + +#console p span { + color: #444 !important; +} - +#console { + background-color: rgba(0, 0, 0, 0.1); +} - +#command { + background-color: rgba(255, 255, 255, 0); + border: 2px solid orange; +} - +#simpleControls thead tr th:nth-child(2) { + border-bottom: none; + width: 10px; +} - -
-
-
- - - -
Select Port
- - - -
- - +#probeControls { + height: 20px; + max-height: 230px; +} -
-
-
X: 0.000
-
Y: 0.000
-
Z: 0.000
-
-
+#probeControls td { + padding: 2px; +} -
- -
+#probeControls input[type="number"] { + width: 4em; +} +#mX, #mY, #mZ, +#wX, #wY, #wZ { + text-align: left; + padding-left: 15px; + margin: 4px; + font-size: 30px; + width: 100%; + border: 1px solid #aaa; + font-weight: bold; +} -
+#probeHomeZero, +#probeX, +#probeY, +#probeZ, +#probeAll { + width: 100%; +} -
- -
- -
+#sendReset, +#sendUnlock, +#sendGrblHelp, +#sendGrblSettings, +#jsCutButton { + margin: 10px; +} -
-
-
-
+#choosePort { + width: 80%; + float: left; +} - +#refreshPorts { + margin-left: 5px; + float: right; +} - +#mStatus { + float: right; + text-align: right; +} -
+/* +#sendZero, +#setInches, +#setMillimeters { + margin-top: 5px; +} +*/ -
+#queueProgress { + min-width: 2em; + color: #000; +} -

-© XYZBots 2015 -

+#GRBLWebHeader { + margin: 0; + display: inline; +} - -
- -
-
-

-
+#betterControls { + width: 230px; + height: 230px; + border: 1px solid orange; + padding; none; + background-color: #fff; +} - -
- -
+#betterControlsPoint { + position: relative; + top: 100px; + left: 100px; + background-color: red; + width: 20px; + height: 20px; + padding: none; +} -
-Drag a .gcode or .nc file to the command box or click Upload GCODE to upload it. -
+#XYZURL { + color: #aaa; + font-size: 0.8em; + margin-top: 20px; +} -
- - - - - -
- -
-Upload GCODE - -
+#mTemp { + float: left; +} + +.instruction_step { + background: rgba(0, 0, 0, 0.5); + position: absolute; + top: -2px; + right: -2px; + min-width: 14px; + height: 14px; + color: #ffffff; + border-radius: 25%; + padding: 2px; + text-align: center; + font-size: 0.7em; +} + + + + + + +
+
+ + +
+ +
+ +
+ +
+ + + +
+ + +
+
+
Select Port
+
+ + + + + +
+
+ + +
+
+
X: 0.000
+
Y: 0.000
+
Z: 0.000
+
+
+
+
+ + +
+
+ +
+ + + +
+ + + +
+
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+
+
+ Webcam +
+
+ + + +
+ + +
+ +
+
+
+
+ + + +
+ + +
+
+ + + + + +
+
+ + +
+
+ Upload GCODE + +
+
+
+ + + +
+ +
+
+
+

+			
+
+ + +
+
+
+
0%
+
+
+
+ + +
+
+
+ +
+
+ + +
+
+ Drag a .gcode or .nc file to the command box or click Upload GCODE to upload it. +
+
+ +
+
diff --git a/i/jquery.powertip/LICENSE.txt b/i/jquery.powertip/LICENSE.txt new file mode 100644 index 0000000..fba0036 --- /dev/null +++ b/i/jquery.powertip/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2013 Steven Benner (http://stevenbenner.com/) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/i/jquery.powertip/css/jquery.powertip-blue.css b/i/jquery.powertip/css/jquery.powertip-blue.css new file mode 100644 index 0000000..2a39d33 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-blue.css @@ -0,0 +1,102 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the blue theme. + */ + +#powerTip { + cursor: default; + background-color: #d2e6fa; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #a5d2fa inset; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #a5d2fa inset; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #a5d2fa inset; + border: 1px solid #4b91d2; + border-radius: 6px; + color: #000000; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #4b91d2; + border-top: 10px solid rgba(75, 145, 210, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #4b91d2; + border-right: 10px solid rgba(75, 145, 210, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #4b91d2; + border-bottom: 10px solid rgba(75, 145, 210, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #4b91d2; + border-left: 10px solid rgba(75, 145, 210, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #4b91d2; + border-top: 10px solid rgba(75, 145, 210, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #4b91d2; + border-bottom: 10px solid rgba(75, 145, 210, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #4b91d2; + border-top: 10px solid rgba(75, 145, 210, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #4b91d2; + border-bottom: 10px solid rgba(75, 145, 210, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip-blue.min.css b/i/jquery.powertip/css/jquery.powertip-blue.min.css new file mode 100644 index 0000000..717fa00 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-blue.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#d2e6fa;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #a5d2fa inset;-moz-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #a5d2fa inset;box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #a5d2fa inset;border:1px solid #4b91d2;border-radius:6px;color:#000;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #4b91d2;border-top:10px solid rgba(75,145,210,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #4b91d2;border-right:10px solid rgba(75,145,210,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #4b91d2;border-bottom:10px solid rgba(75,145,210,.8);top:-10px}#powerTip.w:before{border-left:10px solid #4b91d2;border-left:10px solid rgba(75,145,210,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #4b91d2;border-top:10px solid rgba(75,145,210,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #4b91d2;border-bottom:10px solid rgba(75,145,210,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #4b91d2;border-top:10px solid rgba(75,145,210,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #4b91d2;border-bottom:10px solid rgba(75,145,210,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/css/jquery.powertip-dark.css b/i/jquery.powertip/css/jquery.powertip-dark.css new file mode 100644 index 0000000..c0c4c9f --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-dark.css @@ -0,0 +1,102 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the dark monochrome theme. + */ + +#powerTip { + cursor: default; + background-color: #424242; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.2) inset, 0 -2px 2px #323232 inset; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.2) inset, 0 -2px 2px #323232 inset; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.2) inset, 0 -2px 2px #323232 inset; + border: 1px solid #000000; + border-radius: 6px; + color: #ffffff; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #000000; + border-top: 10px solid rgba(0, 0, 0, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #000000; + border-right: 10px solid rgba(0, 0, 0, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #000000; + border-bottom: 10px solid rgba(0, 0, 0, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #000000; + border-left: 10px solid rgba(0, 0, 0, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #000000; + border-top: 10px solid rgba(0, 0, 0, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #000000; + border-bottom: 10px solid rgba(0, 0, 0, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #000000; + border-top: 10px solid rgba(0, 0, 0, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #000000; + border-bottom: 10px solid rgba(0, 0, 0, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip-dark.min.css b/i/jquery.powertip/css/jquery.powertip-dark.min.css new file mode 100644 index 0000000..9d72a72 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-dark.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#424242;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.2) inset,0 -2px 2px #323232 inset;-moz-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.2) inset,0 -2px 2px #323232 inset;box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.2) inset,0 -2px 2px #323232 inset;border:1px solid #000;border-radius:6px;color:#fff;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #000;border-top:10px solid rgba(0,0,0,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #000;border-right:10px solid rgba(0,0,0,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #000;border-bottom:10px solid rgba(0,0,0,.8);top:-10px}#powerTip.w:before{border-left:10px solid #000;border-left:10px solid rgba(0,0,0,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #000;border-top:10px solid rgba(0,0,0,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #000;border-bottom:10px solid rgba(0,0,0,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #000;border-top:10px solid rgba(0,0,0,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #000;border-bottom:10px solid rgba(0,0,0,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/css/jquery.powertip-green.css b/i/jquery.powertip/css/jquery.powertip-green.css new file mode 100644 index 0000000..cdc84ff --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-green.css @@ -0,0 +1,102 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the green theme. + */ + +#powerTip { + cursor: default; + background-color: #f0ffb9; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.8) inset, 0 -2px 2px #dcf582 inset; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.8) inset, 0 -2px 2px #dcf582 inset; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.8) inset, 0 -2px 2px #dcf582 inset; + border: 1px solid #9bc800; + border-radius: 6px; + color: #000000; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #9bc800; + border-top: 10px solid rgba(155, 200, 0, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #9bc800; + border-right: 10px solid rgba(155, 200, 0, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #9bc800; + border-bottom: 10px solid rgba(155, 200, 0, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #9bc800; + border-left: 10px solid rgba(155, 200, 0, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #9bc800; + border-top: 10px solid rgba(155, 200, 0, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #9bc800; + border-bottom: 10px solid rgba(155, 200, 0, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #9bc800; + border-top: 10px solid rgba(155, 200, 0, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #9bc800; + border-bottom: 10px solid rgba(155, 200, 0, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip-green.min.css b/i/jquery.powertip/css/jquery.powertip-green.min.css new file mode 100644 index 0000000..ad7a644 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-green.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#f0ffb9;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.8) inset,0 -2px 2px #dcf582 inset;-moz-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.8) inset,0 -2px 2px #dcf582 inset;box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.8) inset,0 -2px 2px #dcf582 inset;border:1px solid #9bc800;border-radius:6px;color:#000;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #9bc800;border-top:10px solid rgba(155,200,0,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #9bc800;border-right:10px solid rgba(155,200,0,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #9bc800;border-bottom:10px solid rgba(155,200,0,.8);top:-10px}#powerTip.w:before{border-left:10px solid #9bc800;border-left:10px solid rgba(155,200,0,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #9bc800;border-top:10px solid rgba(155,200,0,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #9bc800;border-bottom:10px solid rgba(155,200,0,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #9bc800;border-top:10px solid rgba(155,200,0,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #9bc800;border-bottom:10px solid rgba(155,200,0,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/css/jquery.powertip-light.css b/i/jquery.powertip/css/jquery.powertip-light.css new file mode 100644 index 0000000..244fd09 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-light.css @@ -0,0 +1,102 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the light monochrome theme. + */ + +#powerTip { + cursor: default; + background-color: #f2f2f2; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #dcdcdc inset; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #dcdcdc inset; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #dcdcdc inset; + border: 1px solid #acacac; + border-radius: 6px; + color: #000000; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #acacac; + border-top: 10px solid rgba(172, 172, 172, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #acacac; + border-right: 10px solid rgba(172, 172, 172, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #acacac; + border-bottom: 10px solid rgba(172, 172, 172, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #acacac; + border-left: 10px solid rgba(172, 172, 172, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #acacac; + border-top: 10px solid rgba(172, 172, 172, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #acacac; + border-bottom: 10px solid rgba(172, 172, 172, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #acacac; + border-top: 10px solid rgba(172, 172, 172, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #acacac; + border-bottom: 10px solid rgba(172, 172, 172, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip-light.min.css b/i/jquery.powertip/css/jquery.powertip-light.min.css new file mode 100644 index 0000000..a990638 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-light.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#f2f2f2;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #dcdcdc inset;-moz-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #dcdcdc inset;box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #dcdcdc inset;border:1px solid #acacac;border-radius:6px;color:#000;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #acacac;border-top:10px solid rgba(172,172,172,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #acacac;border-right:10px solid rgba(172,172,172,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #acacac;border-bottom:10px solid rgba(172,172,172,.8);top:-10px}#powerTip.w:before{border-left:10px solid #acacac;border-left:10px solid rgba(172,172,172,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #acacac;border-top:10px solid rgba(172,172,172,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #acacac;border-bottom:10px solid rgba(172,172,172,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #acacac;border-top:10px solid rgba(172,172,172,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #acacac;border-bottom:10px solid rgba(172,172,172,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/css/jquery.powertip-orange.css b/i/jquery.powertip/css/jquery.powertip-orange.css new file mode 100644 index 0000000..441a45c --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-orange.css @@ -0,0 +1,102 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the orange theme. + */ + +#powerTip { + cursor: default; + background-color: #ffcdaf; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #fab482 inset; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #fab482 inset; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #fab482 inset; + border: 1px solid #f5a550; + border-radius: 6px; + color: #000000; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #f5a550; + border-top: 10px solid rgba(245, 165, 80, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #f5a550; + border-right: 10px solid rgba(245, 165, 80, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #f5a550; + border-bottom: 10px solid rgba(245, 165, 80, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #f5a550; + border-left: 10px solid rgba(245, 165, 80, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #f5a550; + border-top: 10px solid rgba(245, 165, 80, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #f5a550; + border-bottom: 10px solid rgba(245, 165, 80, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #f5a550; + border-top: 10px solid rgba(245, 165, 80, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #f5a550; + border-bottom: 10px solid rgba(245, 165, 80, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip-orange.min.css b/i/jquery.powertip/css/jquery.powertip-orange.min.css new file mode 100644 index 0000000..294f63b --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-orange.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#ffcdaf;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #fab482 inset;-moz-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #fab482 inset;box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #fab482 inset;border:1px solid #f5a550;border-radius:6px;color:#000;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #f5a550;border-top:10px solid rgba(245,165,80,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #f5a550;border-right:10px solid rgba(245,165,80,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #f5a550;border-bottom:10px solid rgba(245,165,80,.8);top:-10px}#powerTip.w:before{border-left:10px solid #f5a550;border-left:10px solid rgba(245,165,80,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #f5a550;border-top:10px solid rgba(245,165,80,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #f5a550;border-bottom:10px solid rgba(245,165,80,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #f5a550;border-top:10px solid rgba(245,165,80,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #f5a550;border-bottom:10px solid rgba(245,165,80,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/css/jquery.powertip-purple.css b/i/jquery.powertip/css/jquery.powertip-purple.css new file mode 100644 index 0000000..8198907 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-purple.css @@ -0,0 +1,102 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the purple theme. + */ + +#powerTip { + cursor: default; + background-color: #ebd2fa; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #d796ff inset; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #d796ff inset; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #d796ff inset; + border: 1px solid #914bd2; + border-radius: 6px; + color: #000000; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #914bd2; + border-top: 10px solid rgba(145, 75, 210, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #914bd2; + border-right: 10px solid rgba(145, 75, 210, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #914bd2; + border-bottom: 10px solid rgba(145, 75, 210, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #914bd2; + border-left: 10px solid rgba(145, 75, 210, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #914bd2; + border-top: 10px solid rgba(145, 75, 210, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #914bd2; + border-bottom: 10px solid rgba(145, 75, 210, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #914bd2; + border-top: 10px solid rgba(145, 75, 210, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #914bd2; + border-bottom: 10px solid rgba(145, 75, 210, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip-purple.min.css b/i/jquery.powertip/css/jquery.powertip-purple.min.css new file mode 100644 index 0000000..7b11db9 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-purple.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#ebd2fa;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #d796ff inset;-moz-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #d796ff inset;box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #d796ff inset;border:1px solid #914bd2;border-radius:6px;color:#000;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #914bd2;border-top:10px solid rgba(145,75,210,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #914bd2;border-right:10px solid rgba(145,75,210,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #914bd2;border-bottom:10px solid rgba(145,75,210,.8);top:-10px}#powerTip.w:before{border-left:10px solid #914bd2;border-left:10px solid rgba(145,75,210,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #914bd2;border-top:10px solid rgba(145,75,210,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #914bd2;border-bottom:10px solid rgba(145,75,210,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #914bd2;border-top:10px solid rgba(145,75,210,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #914bd2;border-bottom:10px solid rgba(145,75,210,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/css/jquery.powertip-red.css b/i/jquery.powertip/css/jquery.powertip-red.css new file mode 100644 index 0000000..1c8c056 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-red.css @@ -0,0 +1,102 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the red theme. + */ + +#powerTip { + cursor: default; + background-color: #ffc8c3; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #f5aa9b inset; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #f5aa9b inset; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.5) inset, 0 -2px 2px #f5aa9b inset; + border: 1px solid #eb5037; + border-radius: 6px; + color: #000000; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #eb5037; + border-top: 10px solid rgba(235, 80, 55, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #eb5037; + border-right: 10px solid rgba(235, 80, 55, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #eb5037; + border-bottom: 10px solid rgba(235, 80, 55, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #eb5037; + border-left: 10px solid rgba(235, 80, 55, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #eb5037; + border-top: 10px solid rgba(235, 80, 55, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #eb5037; + border-bottom: 10px solid rgba(235, 80, 55, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #eb5037; + border-top: 10px solid rgba(235, 80, 55, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #eb5037; + border-bottom: 10px solid rgba(235, 80, 55, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip-red.min.css b/i/jquery.powertip/css/jquery.powertip-red.min.css new file mode 100644 index 0000000..c50b8de --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-red.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#ffc8c3;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #f5aa9b inset;-moz-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #f5aa9b inset;box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.5) inset,0 -2px 2px #f5aa9b inset;border:1px solid #eb5037;border-radius:6px;color:#000;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #eb5037;border-top:10px solid rgba(235,80,55,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #eb5037;border-right:10px solid rgba(235,80,55,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #eb5037;border-bottom:10px solid rgba(235,80,55,.8);top:-10px}#powerTip.w:before{border-left:10px solid #eb5037;border-left:10px solid rgba(235,80,55,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #eb5037;border-top:10px solid rgba(235,80,55,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #eb5037;border-bottom:10px solid rgba(235,80,55,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #eb5037;border-top:10px solid rgba(235,80,55,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #eb5037;border-bottom:10px solid rgba(235,80,55,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/css/jquery.powertip-yellow.css b/i/jquery.powertip/css/jquery.powertip-yellow.css new file mode 100644 index 0000000..dbf0a6a --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-yellow.css @@ -0,0 +1,102 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the yellow theme. + */ + +#powerTip { + cursor: default; + background-color: #ffffb4; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.8) inset, 0 -2px 2px #fafa6e inset; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.8) inset, 0 -2px 2px #fafa6e inset; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 1px rgba(255, 255, 255, 0.8) inset, 0 -2px 2px #fafa6e inset; + border: 1px solid #fafa50; + border-radius: 6px; + color: #000000; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #fafa50; + border-top: 10px solid rgba(250, 250, 80, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #fafa50; + border-right: 10px solid rgba(250, 250, 80, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #fafa50; + border-bottom: 10px solid rgba(250, 250, 80, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #fafa50; + border-left: 10px solid rgba(250, 250, 80, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #fafa50; + border-top: 10px solid rgba(250, 250, 80, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #fafa50; + border-bottom: 10px solid rgba(250, 250, 80, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #fafa50; + border-top: 10px solid rgba(250, 250, 80, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #fafa50; + border-bottom: 10px solid rgba(250, 250, 80, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip-yellow.min.css b/i/jquery.powertip/css/jquery.powertip-yellow.min.css new file mode 100644 index 0000000..2ecae25 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip-yellow.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#ffffb4;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.8) inset,0 -2px 2px #fafa6e inset;-moz-box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.8) inset,0 -2px 2px #fafa6e inset;box-shadow:0 1px 1px rgba(0,0,0,.15),0 2px 1px rgba(255,255,255,.8) inset,0 -2px 2px #fafa6e inset;border:1px solid #fafa50;border-radius:6px;color:#000;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #fafa50;border-top:10px solid rgba(250,250,80,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #fafa50;border-right:10px solid rgba(250,250,80,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #fafa50;border-bottom:10px solid rgba(250,250,80,.8);top:-10px}#powerTip.w:before{border-left:10px solid #fafa50;border-left:10px solid rgba(250,250,80,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #fafa50;border-top:10px solid rgba(250,250,80,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #fafa50;border-bottom:10px solid rgba(250,250,80,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #fafa50;border-top:10px solid rgba(250,250,80,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #fafa50;border-bottom:10px solid rgba(250,250,80,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/css/jquery.powertip.css b/i/jquery.powertip/css/jquery.powertip.css new file mode 100644 index 0000000..5811bfb --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip.css @@ -0,0 +1,99 @@ +/** + * PowerTip + * https://stevenbenner.github.io/jquery-powertip/ + * + * Stylesheet for the monochrome (default) theme. + */ + +#powerTip { + cursor: default; + background-color: #333; + background-color: rgba(0, 0, 0, 0.8); + border-radius: 6px; + color: #fff; + display: none; + padding: 10px; + position: absolute; + white-space: nowrap; + z-index: 2147483647; +} +#powerTip:before { + content: ""; + position: absolute; +} +#powerTip.n:before, #powerTip.s:before { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + left: 50%; + margin-left: -5px; +} +#powerTip.e:before, #powerTip.w:before { + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + margin-top: -5px; + top: 50%; +} +#powerTip.n:before { + border-top: 10px solid #333; + border-top: 10px solid rgba(0, 0, 0, 0.8); + bottom: -10px; +} +#powerTip.e:before { + border-right: 10px solid #333; + border-right: 10px solid rgba(0, 0, 0, 0.8); + left: -10px; +} +#powerTip.s:before { + border-bottom: 10px solid #333; + border-bottom: 10px solid rgba(0, 0, 0, 0.8); + top: -10px; +} +#powerTip.w:before { + border-left: 10px solid #333; + border-left: 10px solid rgba(0, 0, 0, 0.8); + right: -10px; +} +#powerTip.ne:before, #powerTip.se:before { + border-right: 10px solid transparent; + border-left: 0; + left: 10px; +} +#powerTip.nw:before, #powerTip.sw:before { + border-left: 10px solid transparent; + border-right: 0; + right: 10px; +} +#powerTip.ne:before, #powerTip.nw:before { + border-top: 10px solid #333; + border-top: 10px solid rgba(0, 0, 0, 0.8); + bottom: -10px; +} +#powerTip.se:before, #powerTip.sw:before { + border-bottom: 10px solid #333; + border-bottom: 10px solid rgba(0, 0, 0, 0.8); + top: -10px; +} +#powerTip.nw-alt:before, #powerTip.ne-alt:before, +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: 10px solid #333; + border-top: 10px solid rgba(0, 0, 0, 0.8); + bottom: -10px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 10px; +} +#powerTip.ne-alt:before { + left: auto; + right: 10px; +} +#powerTip.sw-alt:before, #powerTip.se-alt:before { + border-top: none; + border-bottom: 10px solid #333; + border-bottom: 10px solid rgba(0, 0, 0, 0.8); + bottom: auto; + top: -10px; +} +#powerTip.se-alt:before { + left: auto; + right: 10px; +} diff --git a/i/jquery.powertip/css/jquery.powertip.min.css b/i/jquery.powertip/css/jquery.powertip.min.css new file mode 100644 index 0000000..0747f02 --- /dev/null +++ b/i/jquery.powertip/css/jquery.powertip.min.css @@ -0,0 +1 @@ +#powerTip{cursor:default;background-color:#333;background-color:rgba(0,0,0,.8);border-radius:6px;color:#fff;display:none;padding:10px;position:absolute;white-space:nowrap;z-index:2147483647}#powerTip:before{content:"";position:absolute}#powerTip.n:before,#powerTip.s:before{border-right:5px solid transparent;border-left:5px solid transparent;left:50%;margin-left:-5px}#powerTip.e:before,#powerTip.w:before{border-bottom:5px solid transparent;border-top:5px solid transparent;margin-top:-5px;top:50%}#powerTip.n:before{border-top:10px solid #333;border-top:10px solid rgba(0,0,0,.8);bottom:-10px}#powerTip.e:before{border-right:10px solid #333;border-right:10px solid rgba(0,0,0,.8);left:-10px}#powerTip.s:before{border-bottom:10px solid #333;border-bottom:10px solid rgba(0,0,0,.8);top:-10px}#powerTip.w:before{border-left:10px solid #333;border-left:10px solid rgba(0,0,0,.8);right:-10px}#powerTip.ne:before,#powerTip.se:before{border-right:10px solid transparent;border-left:0;left:10px}#powerTip.nw:before,#powerTip.sw:before{border-left:10px solid transparent;border-right:0;right:10px}#powerTip.ne:before,#powerTip.nw:before{border-top:10px solid #333;border-top:10px solid rgba(0,0,0,.8);bottom:-10px}#powerTip.se:before,#powerTip.sw:before{border-bottom:10px solid #333;border-bottom:10px solid rgba(0,0,0,.8);top:-10px}#powerTip.ne-alt:before,#powerTip.nw-alt:before,#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:10px solid #333;border-top:10px solid rgba(0,0,0,.8);bottom:-10px;border-left:5px solid transparent;border-right:5px solid transparent;left:10px}#powerTip.ne-alt:before,#powerTip.se-alt:before{left:auto;right:10px}#powerTip.se-alt:before,#powerTip.sw-alt:before{border-top:none;border-bottom:10px solid #333;border-bottom:10px solid rgba(0,0,0,.8);bottom:auto;top:-10px} \ No newline at end of file diff --git a/i/jquery.powertip/examples/examples.html b/i/jquery.powertip/examples/examples.html new file mode 100644 index 0000000..b1eb4e2 --- /dev/null +++ b/i/jquery.powertip/examples/examples.html @@ -0,0 +1,150 @@ + + + + + PowerTip Examples + + + + + + + + + + + + + +

PowerTip Examples

+ + +
+

Placement examples

+
+ + + + +
+ +
+ + + + + +
+
+ + + +
+

Mouse follow example

+
+ The PowerTip for this box will follow the mouse. +
+
+ + + +
+

Mouse on to tooltip example

+
+ The PowerTip for this box will appear on the right and you will be able to interact with its content. +
+
+ + + +
+

API examples

+ + + +
+ Delegated manual mouseover, with lazy-loaded tooltip: + + +
+
+ + + + diff --git a/i/jquery.powertip/examples/examples_svg.html b/i/jquery.powertip/examples/examples_svg.html new file mode 100644 index 0000000..fed86d3 --- /dev/null +++ b/i/jquery.powertip/examples/examples_svg.html @@ -0,0 +1,131 @@ + + + + + PowerTip SVG Examples + + + + + + + + + + + + + +

PowerTip SVG Examples

+ + +
+

Simple Placement Examples

+
+ + + + + + + + + + + + + + ​ +
+
+ + + +
+

Complex Shape Placement Examples

+
+ + + + + + + + + + + + + + + Text + ​ +
+
+ + + +
+

Rotation Placement Examples

+
+ + + + + + + + + + + ​ +
+
+ + + + diff --git a/i/jquery.powertip/jquery.powertip.js b/i/jquery.powertip/jquery.powertip.js new file mode 100644 index 0000000..b91552d --- /dev/null +++ b/i/jquery.powertip/jquery.powertip.js @@ -0,0 +1,1355 @@ +/*! + PowerTip v1.3.0 (2017-01-15) + https://stevenbenner.github.io/jquery-powertip/ + Copyright (c) 2017 Steven Benner (http://stevenbenner.com/). + Released under MIT license. + https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt +*/ +(function(root, factory) { + // support loading the plugin via common patterns + if (typeof define === 'function' && define.amd) { + // load the plugin as an amd module + define([ 'jquery' ], factory); + } else if (typeof module === 'object' && module.exports) { + // load the plugin as a commonjs module + module.exports = factory(require('jquery')); + } else { + // load the plugin as a global + factory(root.jQuery); + } +}(this, function($) { + + // useful private variables + var $document = $(document), + $window = $(window), + $body = $('body'); + + // constants + var DATA_DISPLAYCONTROLLER = 'displayController', + DATA_HASACTIVEHOVER = 'hasActiveHover', + DATA_FORCEDOPEN = 'forcedOpen', + DATA_HASMOUSEMOVE = 'hasMouseMove', + DATA_MOUSEONTOTIP = 'mouseOnToPopup', + DATA_ORIGINALTITLE = 'originalTitle', + DATA_POWERTIP = 'powertip', + DATA_POWERTIPJQ = 'powertipjq', + DATA_POWERTIPTARGET = 'powertiptarget', + EVENT_NAMESPACE = '.powertip', + RAD2DEG = 180 / Math.PI; + + /** + * Session data + * Private properties global to all powerTip instances + */ + var session = { + elements: null, + tooltips: null, + isTipOpen: false, + isFixedTipOpen: false, + isClosing: false, + tipOpenImminent: false, + activeHover: null, + currentX: 0, + currentY: 0, + previousX: 0, + previousY: 0, + desyncTimeout: null, + closeDelayTimeout: null, + mouseTrackingActive: false, + delayInProgress: false, + windowWidth: 0, + windowHeight: 0, + scrollTop: 0, + scrollLeft: 0 + }; + + /** + * Collision enumeration + * @enum {number} + */ + var Collision = { + none: 0, + top: 1, + bottom: 2, + left: 4, + right: 8 + }; + + /** + * Display hover tooltips on the matched elements. + * @param {(Object|string)=} opts The options object to use for the plugin, or + * the name of a method to invoke on the first matched element. + * @param {*=} [arg] Argument for an invoked method (optional). + * @return {jQuery} jQuery object for the matched selectors. + */ + $.fn.powerTip = function(opts, arg) { + var targetElements = this, + options, + tipController; + + // don't do any work if there were no matched elements + if (!targetElements.length) { + return targetElements; + } + + // handle api method calls on the plugin, e.g. powerTip('hide') + if ($.type(opts) === 'string' && $.powerTip[opts]) { + return $.powerTip[opts].call(targetElements, targetElements, arg); + } + + // extend options and instantiate TooltipController + options = $.extend({}, $.fn.powerTip.defaults, opts); + tipController = new TooltipController(options); + + // hook mouse and viewport dimension tracking + initTracking(); + + // setup the elements + targetElements.each(function elementSetup() { + var $this = $(this), + dataPowertip = $this.data(DATA_POWERTIP), + dataElem = $this.data(DATA_POWERTIPJQ), + dataTarget = $this.data(DATA_POWERTIPTARGET), + title; + + // handle repeated powerTip calls on the same element by destroying the + // original instance hooked to it and replacing it with this call + if ($this.data(DATA_DISPLAYCONTROLLER)) { + $.powerTip.destroy($this); + } + + // attempt to use title attribute text if there is no data-powertip, + // data-powertipjq or data-powertiptarget. If we do use the title + // attribute, delete the attribute so the browser will not show it + title = $this.attr('title'); + if (!dataPowertip && !dataTarget && !dataElem && title) { + $this.data(DATA_POWERTIP, title); + $this.data(DATA_ORIGINALTITLE, title); + $this.removeAttr('title'); + } + + // create hover controllers for each element + $this.data( + DATA_DISPLAYCONTROLLER, + new DisplayController($this, options, tipController) + ); + }); + + // attach events to matched elements if the manual option is not enabled + if (!options.manual) { + // attach open events + $.each(options.openEvents, function(idx, evt) { + if ($.inArray(evt, options.closeEvents) > -1) { + // event is in both openEvents and closeEvents, so toggle it + targetElements.on(evt + EVENT_NAMESPACE, function elementToggle(event) { + $.powerTip.toggle(this, event); + }); + } else { + targetElements.on(evt + EVENT_NAMESPACE, function elementOpen(event) { + $.powerTip.show(this, event); + }); + } + }); + + // attach close events + $.each(options.closeEvents, function(idx, evt) { + if ($.inArray(evt, options.openEvents) < 0) { + targetElements.on(evt + EVENT_NAMESPACE, function elementClose(event) { + // set immediate to true for any event without mouse info + $.powerTip.hide(this, !isMouseEvent(event)); + }); + } + }); + + // attach escape key close event + targetElements.on('keydown' + EVENT_NAMESPACE, function elementKeyDown(event) { + // always close tooltip when the escape key is pressed + if (event.keyCode === 27) { + $.powerTip.hide(this, true); + } + }); + } + + // remember elements that the plugin is attached to + session.elements = session.elements ? session.elements.add(targetElements) : targetElements; + + return targetElements; + }; + + /** + * Default options for the powerTip plugin. + */ + $.fn.powerTip.defaults = { + fadeInTime: 200, + fadeOutTime: 100, + followMouse: false, + popupId: 'powerTip', + popupClass: null, + intentSensitivity: 7, + intentPollInterval: 100, + closeDelay: 100, + placement: 'n', + smartPlacement: false, + offset: 10, + mouseOnToPopup: false, + manual: false, + openEvents: [ 'mouseenter', 'focus' ], + closeEvents: [ 'mouseleave', 'blur' ] + }; + + /** + * Default smart placement priority lists. + * The first item in the array is the highest priority, the last is the lowest. + * The last item is also the default, which will be used if all previous options + * do not fit. + */ + $.fn.powerTip.smartPlacementLists = { + n: [ 'n', 'ne', 'nw', 's' ], + e: [ 'e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e' ], + s: [ 's', 'se', 'sw', 'n' ], + w: [ 'w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w' ], + nw: [ 'nw', 'w', 'sw', 'n', 's', 'se', 'nw' ], + ne: [ 'ne', 'e', 'se', 'n', 's', 'sw', 'ne' ], + sw: [ 'sw', 'w', 'nw', 's', 'n', 'ne', 'sw' ], + se: [ 'se', 'e', 'ne', 's', 'n', 'nw', 'se' ], + 'nw-alt': [ 'nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e' ], + 'ne-alt': [ 'ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w' ], + 'sw-alt': [ 'sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e' ], + 'se-alt': [ 'se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w' ] + }; + + /** + * Public API + */ + $.powerTip = { + /** + * Attempts to show the tooltip for the specified element. + * @param {jQuery|Element} element The element to open the tooltip for. + * @param {jQuery.Event=} event jQuery event for hover intent and mouse + * tracking (optional). + * @return {jQuery|Element} The original jQuery object or DOM Element. + */ + show: function apiShowTip(element, event) { + // if we were given a mouse event then run the hover intent testing, + // otherwise, simply show the tooltip asap + if (isMouseEvent(event)) { + trackMouse(event); + session.previousX = event.pageX; + session.previousY = event.pageY; + $(element).data(DATA_DISPLAYCONTROLLER).show(); + } else { + $(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true); + } + return element; + }, + + /** + * Repositions the tooltip on the element. + * @param {jQuery|Element} element The element the tooltip is shown for. + * @return {jQuery|Element} The original jQuery object or DOM Element. + */ + reposition: function apiResetPosition(element) { + $(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition(); + return element; + }, + + /** + * Attempts to close any open tooltips. + * @param {(jQuery|Element)=} element The element with the tooltip that + * should be closed (optional). + * @param {boolean=} immediate Disable close delay (optional). + * @return {jQuery|Element|undefined} The original jQuery object or DOM + * Element, if one was specified. + */ + hide: function apiCloseTip(element, immediate) { + var displayController; + + // set immediate to true when no element is specified + immediate = element ? immediate : true; + + // find the relevant display controller + if (element) { + displayController = $(element).first().data(DATA_DISPLAYCONTROLLER); + } else if (session.activeHover) { + displayController = session.activeHover.data(DATA_DISPLAYCONTROLLER); + } + + // if found, hide the tip + if (displayController) { + displayController.hide(immediate); + } + + return element; + }, + + /** + * Toggles the tooltip for the specified element. This will open a closed + * tooltip, or close an open tooltip. + * @param {jQuery|Element} element The element with the tooltip that + * should be toggled. + * @param {jQuery.Event=} event jQuery event for hover intent and mouse + * tracking (optional). + * @return {jQuery|Element} The original jQuery object or DOM Element. + */ + toggle: function apiToggle(element, event) { + if (session.activeHover && session.activeHover.is(element)) { + // tooltip for element is active, so close it + $.powerTip.hide(element, !isMouseEvent(event)); + } else { + // tooltip for element is not active, so open it + $.powerTip.show(element, event); + } + return element; + }, + + /** + * Destroy and roll back any powerTip() instance on the specified elements. + * If no elements are specified then all elements that the plugin is + * currently attached to will be rolled back. + * @param {(jQuery|Element)=} element The element with the powerTip instance. + * @return {jQuery|Element|undefined} The original jQuery object or DOM + * Element, if one was specified. + */ + destroy: function apiDestroy(element) { + var $element = element ? $(element) : session.elements; + + // if the plugin is not hooked to any elements then there is no point + // trying to destroy anything, or dealing with the possible errors + if (!session.elements || session.elements.length === 0) { + return element; + } + + // unhook events and destroy plugin changes to each element + $element.off(EVENT_NAMESPACE).each(function destroy() { + var $this = $(this), + dataAttributes = [ + DATA_ORIGINALTITLE, + DATA_DISPLAYCONTROLLER, + DATA_HASACTIVEHOVER, + DATA_FORCEDOPEN + ]; + + // revert title attribute + if ($this.data(DATA_ORIGINALTITLE)) { + $this.attr('title', $this.data(DATA_ORIGINALTITLE)); + dataAttributes.push(DATA_POWERTIP); + } + + // remove data attributes + $this.removeData(dataAttributes); + }); + + // remove destroyed element from active elements collection + session.elements = session.elements.not($element); + + // if there are no active elements left then we will unhook all of the + // events that we've bound code to and remove the tooltip elements + if (session.elements.length === 0) { + $window.off(EVENT_NAMESPACE); + $document.off(EVENT_NAMESPACE); + session.mouseTrackingActive = false; + session.tooltips.remove(); + session.tooltips = null; + } + + return element; + } + }; + + // API aliasing + $.powerTip.showTip = $.powerTip.show; + $.powerTip.closeTip = $.powerTip.hide; + + /** + * Creates a new CSSCoordinates object. + * @private + * @constructor + */ + function CSSCoordinates() { + var me = this; + + // initialize object properties + me.top = 'auto'; + me.left = 'auto'; + me.right = 'auto'; + me.bottom = 'auto'; + + /** + * Set a property to a value. + * @private + * @param {string} property The name of the property. + * @param {number} value The value of the property. + */ + me.set = function(property, value) { + if ($.isNumeric(value)) { + me[property] = Math.round(value); + } + }; + } + + /** + * Creates a new tooltip display controller. + * @private + * @constructor + * @param {jQuery} element The element that this controller will handle. + * @param {Object} options Options object containing settings. + * @param {TooltipController} tipController The TooltipController object for + * this instance. + */ + function DisplayController(element, options, tipController) { + var hoverTimer = null, + myCloseDelay = null; + + /** + * Begins the process of showing a tooltip. + * @private + * @param {boolean=} immediate Skip intent testing (optional). + * @param {boolean=} forceOpen Ignore cursor position and force tooltip to + * open (optional). + */ + function openTooltip(immediate, forceOpen) { + cancelTimer(); + if (!element.data(DATA_HASACTIVEHOVER)) { + if (!immediate) { + session.tipOpenImminent = true; + hoverTimer = setTimeout( + function intentDelay() { + hoverTimer = null; + checkForIntent(); + }, + options.intentPollInterval + ); + } else { + if (forceOpen) { + element.data(DATA_FORCEDOPEN, true); + } + closeAnyDelayed(); + tipController.showTip(element); + } + } else { + // cursor left and returned to this element, cancel close + cancelClose(); + } + } + + /** + * Begins the process of closing a tooltip. + * @private + * @param {boolean=} disableDelay Disable close delay (optional). + */ + function closeTooltip(disableDelay) { + // if this instance already has a close delay in progress then halt it + if (myCloseDelay) { + myCloseDelay = session.closeDelayTimeout = clearTimeout(myCloseDelay); + session.delayInProgress = false; + } + cancelTimer(); + session.tipOpenImminent = false; + if (element.data(DATA_HASACTIVEHOVER)) { + element.data(DATA_FORCEDOPEN, false); + if (!disableDelay) { + session.delayInProgress = true; + session.closeDelayTimeout = setTimeout( + function closeDelay() { + session.closeDelayTimeout = null; + tipController.hideTip(element); + session.delayInProgress = false; + myCloseDelay = null; + }, + options.closeDelay + ); + // save internal reference close delay id so we can check if the + // active close delay belongs to this instance + myCloseDelay = session.closeDelayTimeout; + } else { + tipController.hideTip(element); + } + } + } + + /** + * Checks mouse position to make sure that the user intended to hover on the + * specified element before showing the tooltip. + * @private + */ + function checkForIntent() { + // calculate mouse position difference + var xDifference = Math.abs(session.previousX - session.currentX), + yDifference = Math.abs(session.previousY - session.currentY), + totalDifference = xDifference + yDifference; + + // check if difference has passed the sensitivity threshold + if (totalDifference < options.intentSensitivity) { + cancelClose(); + closeAnyDelayed(); + tipController.showTip(element); + } else { + // try again + session.previousX = session.currentX; + session.previousY = session.currentY; + openTooltip(); + } + } + + /** + * Cancels active hover timer. + * @private + * @param {boolean=} stopClose Cancel any active close delay timer. + */ + function cancelTimer(stopClose) { + hoverTimer = clearTimeout(hoverTimer); + // cancel the current close delay if the active close delay is for this + // element or the stopClose argument is true + if (session.closeDelayTimeout && myCloseDelay === session.closeDelayTimeout || stopClose) { + cancelClose(); + } + } + + /** + * Cancels any active close delay timer. + * @private + */ + function cancelClose() { + session.closeDelayTimeout = clearTimeout(session.closeDelayTimeout); + session.delayInProgress = false; + } + + /** + * Asks any tooltips waiting on their close delay to close now. + * @private + */ + function closeAnyDelayed() { + // if another element is waiting for its close delay then we should ask + // it to close immediately so we can proceed without unexpected timeout + // code being run during this tooltip's lifecycle + if (session.delayInProgress && session.activeHover && !session.activeHover.is(element)) { + session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true); + } + } + + /** + * Repositions the tooltip on this element. + * @private + */ + function repositionTooltip() { + tipController.resetPosition(element); + } + + // expose the methods + this.show = openTooltip; + this.hide = closeTooltip; + this.cancel = cancelTimer; + this.resetPosition = repositionTooltip; + } + + /** + * Creates a new Placement Calculator. + * @private + * @constructor + */ + function PlacementCalculator() { + /** + * Compute the CSS position to display a tooltip at the specified placement + * relative to the specified element. + * @private + * @param {jQuery} element The element that the tooltip should target. + * @param {string} placement The placement for the tooltip. + * @param {number} tipWidth Width of the tooltip element in pixels. + * @param {number} tipHeight Height of the tooltip element in pixels. + * @param {number} offset Distance to offset tooltips in pixels. + * @return {CSSCoordinates} A CSSCoordinates object with the position. + */ + function computePlacementCoords(element, placement, tipWidth, tipHeight, offset) { + var placementBase = placement.split('-')[0], // ignore 'alt' for corners + coords = new CSSCoordinates(), + position; + + if (isSvgElement(element)) { + position = getSvgPlacement(element, placementBase); + } else { + position = getHtmlPlacement(element, placementBase); + } + + // calculate the appropriate x and y position in the document + switch (placement) { + case 'n': + coords.set('left', position.left - (tipWidth / 2)); + coords.set('bottom', session.windowHeight - position.top + offset); + break; + case 'e': + coords.set('left', position.left + offset); + coords.set('top', position.top - (tipHeight / 2)); + break; + case 's': + coords.set('left', position.left - (tipWidth / 2)); + coords.set('top', position.top + offset); + break; + case 'w': + coords.set('top', position.top - (tipHeight / 2)); + coords.set('right', session.windowWidth - position.left + offset); + break; + case 'nw': + coords.set('bottom', session.windowHeight - position.top + offset); + coords.set('right', session.windowWidth - position.left - 20); + break; + case 'nw-alt': + coords.set('left', position.left); + coords.set('bottom', session.windowHeight - position.top + offset); + break; + case 'ne': + coords.set('left', position.left - 20); + coords.set('bottom', session.windowHeight - position.top + offset); + break; + case 'ne-alt': + coords.set('bottom', session.windowHeight - position.top + offset); + coords.set('right', session.windowWidth - position.left); + break; + case 'sw': + coords.set('top', position.top + offset); + coords.set('right', session.windowWidth - position.left - 20); + break; + case 'sw-alt': + coords.set('left', position.left); + coords.set('top', position.top + offset); + break; + case 'se': + coords.set('left', position.left - 20); + coords.set('top', position.top + offset); + break; + case 'se-alt': + coords.set('top', position.top + offset); + coords.set('right', session.windowWidth - position.left); + break; + } + + return coords; + } + + /** + * Finds the tooltip attachment point in the document for a HTML DOM element + * for the specified placement. + * @private + * @param {jQuery} element The element that the tooltip should target. + * @param {string} placement The placement for the tooltip. + * @return {Object} An object with the top,left position values. + */ + function getHtmlPlacement(element, placement) { + var objectOffset = element.offset(), + objectWidth = element.outerWidth(), + objectHeight = element.outerHeight(), + left, + top; + + // calculate the appropriate x and y position in the document + switch (placement) { + case 'n': + left = objectOffset.left + objectWidth / 2; + top = objectOffset.top; + break; + case 'e': + left = objectOffset.left + objectWidth; + top = objectOffset.top + objectHeight / 2; + break; + case 's': + left = objectOffset.left + objectWidth / 2; + top = objectOffset.top + objectHeight; + break; + case 'w': + left = objectOffset.left; + top = objectOffset.top + objectHeight / 2; + break; + case 'nw': + left = objectOffset.left; + top = objectOffset.top; + break; + case 'ne': + left = objectOffset.left + objectWidth; + top = objectOffset.top; + break; + case 'sw': + left = objectOffset.left; + top = objectOffset.top + objectHeight; + break; + case 'se': + left = objectOffset.left + objectWidth; + top = objectOffset.top + objectHeight; + break; + } + + return { + top: top, + left: left + }; + } + + /** + * Finds the tooltip attachment point in the document for a SVG element for + * the specified placement. + * @private + * @param {jQuery} element The element that the tooltip should target. + * @param {string} placement The placement for the tooltip. + * @return {Object} An object with the top,left position values. + */ + function getSvgPlacement(element, placement) { + var svgElement = element.closest('svg')[0], + domElement = element[0], + point = svgElement.createSVGPoint(), + boundingBox = domElement.getBBox(), + matrix = domElement.getScreenCTM(), + halfWidth = boundingBox.width / 2, + halfHeight = boundingBox.height / 2, + placements = [], + placementKeys = [ 'nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w' ], + coords, + rotation, + steps, + x; + + function pushPlacement() { + placements.push(point.matrixTransform(matrix)); + } + + // get bounding box corners and midpoints + point.x = boundingBox.x; + point.y = boundingBox.y; + pushPlacement(); + point.x += halfWidth; + pushPlacement(); + point.x += halfWidth; + pushPlacement(); + point.y += halfHeight; + pushPlacement(); + point.y += halfHeight; + pushPlacement(); + point.x -= halfWidth; + pushPlacement(); + point.x -= halfWidth; + pushPlacement(); + point.y -= halfHeight; + pushPlacement(); + + // determine rotation + if (placements[0].y !== placements[1].y || placements[0].x !== placements[7].x) { + rotation = Math.atan2(matrix.b, matrix.a) * RAD2DEG; + steps = Math.ceil(((rotation % 360) - 22.5) / 45); + if (steps < 1) { + steps += 8; + } + while (steps--) { + placementKeys.push(placementKeys.shift()); + } + } + + // find placement + for (x = 0; x < placements.length; x++) { + if (placementKeys[x] === placement) { + coords = placements[x]; + break; + } + } + + return { + top: coords.y + session.scrollTop, + left: coords.x + session.scrollLeft + }; + } + + // expose methods + this.compute = computePlacementCoords; + } + + /** + * Creates a new tooltip controller. + * @private + * @constructor + * @param {Object} options Options object containing settings. + */ + function TooltipController(options) { + var placementCalculator = new PlacementCalculator(), + tipElement = $('#' + options.popupId); + + // build and append tooltip div if it does not already exist + if (tipElement.length === 0) { + tipElement = $('
', { id: options.popupId }); + // grab body element if it was not populated when the script loaded + // note: this hack exists solely for jsfiddle support + if ($body.length === 0) { + $body = $('body'); + } + $body.append(tipElement); + // remember the tooltip elements that the plugin has created + session.tooltips = session.tooltips ? session.tooltips.add(tipElement) : tipElement; + } + + // hook mousemove for cursor follow tooltips + if (options.followMouse) { + // only one positionTipOnCursor hook per tooltip element, please + if (!tipElement.data(DATA_HASMOUSEMOVE)) { + $document.on('mousemove' + EVENT_NAMESPACE, positionTipOnCursor); + $window.on('scroll' + EVENT_NAMESPACE, positionTipOnCursor); + tipElement.data(DATA_HASMOUSEMOVE, true); + } + } + + /** + * Gives the specified element the active-hover state and queues up the + * showTip function. + * @private + * @param {jQuery} element The element that the tooltip should target. + */ + function beginShowTip(element) { + element.data(DATA_HASACTIVEHOVER, true); + // show tooltip, asap + tipElement.queue(function queueTipInit(next) { + showTip(element); + next(); + }); + } + + /** + * Shows the tooltip, as soon as possible. + * @private + * @param {jQuery} element The element that the tooltip should target. + */ + function showTip(element) { + var tipContent; + + // it is possible, especially with keyboard navigation, to move on to + // another element with a tooltip during the queue to get to this point + // in the code. if that happens then we need to not proceed or we may + // have the fadeout callback for the last tooltip execute immediately + // after this code runs, causing bugs. + if (!element.data(DATA_HASACTIVEHOVER)) { + return; + } + + // if the tooltip is open and we got asked to open another one then the + // old one is still in its fadeOut cycle, so wait and try again + if (session.isTipOpen) { + if (!session.isClosing) { + hideTip(session.activeHover); + } + tipElement.delay(100).queue(function queueTipAgain(next) { + showTip(element); + next(); + }); + return; + } + + // trigger powerTipPreRender event + element.trigger('powerTipPreRender'); + + // set tooltip content + tipContent = getTooltipContent(element); + if (tipContent) { + tipElement.empty().append(tipContent); + } else { + // we have no content to display, give up + return; + } + + // trigger powerTipRender event + element.trigger('powerTipRender'); + + session.activeHover = element; + session.isTipOpen = true; + + tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup); + + // set tooltip position + if (!options.followMouse) { + positionTipOnElement(element); + session.isFixedTipOpen = true; + } else { + positionTipOnCursor(); + } + + // add custom class to tooltip element + tipElement.addClass(options.popupClass); + + // close tooltip when clicking anywhere on the page, with the exception + // of the tooltip's trigger element and any elements that are within a + // tooltip that has 'mouseOnToPopup' option enabled + if (!element.data(DATA_FORCEDOPEN)) { + $document.on('click' + EVENT_NAMESPACE, function documentClick(event) { + var target = event.target; + if (target !== element[0]) { + if (options.mouseOnToPopup) { + if (target !== tipElement[0] && !$.contains(tipElement[0], target)) { + $.powerTip.hide(); + } + } else { + $.powerTip.hide(); + } + } + }); + } + + // if we want to be able to mouse on to the tooltip then we need to + // attach hover events to the tooltip that will cancel a close request + // on mouseenter and start a new close request on mouseleave + // only hook these listeners if we're not in manual mode + if (options.mouseOnToPopup && !options.manual) { + tipElement.on('mouseenter' + EVENT_NAMESPACE, function tipMouseEnter() { + // check activeHover in case the mouse cursor entered the + // tooltip during the fadeOut and close cycle + if (session.activeHover) { + session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel(); + } + }); + tipElement.on('mouseleave' + EVENT_NAMESPACE, function tipMouseLeave() { + // check activeHover in case the mouse cursor left the tooltip + // during the fadeOut and close cycle + if (session.activeHover) { + session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(); + } + }); + } + + // fadein + tipElement.fadeIn(options.fadeInTime, function fadeInCallback() { + // start desync polling + if (!session.desyncTimeout) { + session.desyncTimeout = setInterval(closeDesyncedTip, 500); + } + + // trigger powerTipOpen event + element.trigger('powerTipOpen'); + }); + } + + /** + * Hides the tooltip. + * @private + * @param {jQuery} element The element that the tooltip should target. + */ + function hideTip(element) { + // reset session + session.isClosing = true; + session.isTipOpen = false; + + // stop desync polling + session.desyncTimeout = clearInterval(session.desyncTimeout); + + // reset element state + element.data(DATA_HASACTIVEHOVER, false); + element.data(DATA_FORCEDOPEN, false); + + // remove document click handler + $document.off('click' + EVENT_NAMESPACE); + + // unbind the mouseOnToPopup events if they were set + tipElement.off(EVENT_NAMESPACE); + + // fade out + tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() { + var coords = new CSSCoordinates(); + + // reset session and tooltip element + session.activeHover = null; + session.isClosing = false; + session.isFixedTipOpen = false; + tipElement.removeClass(); + + // support mouse-follow and fixed position tips at the same time by + // moving the tooltip to the last cursor location after it is hidden + coords.set('top', session.currentY + options.offset); + coords.set('left', session.currentX + options.offset); + tipElement.css(coords); + + // trigger powerTipClose event + element.trigger('powerTipClose'); + }); + } + + /** + * Moves the tooltip to the users mouse cursor. + * @private + */ + function positionTipOnCursor() { + // to support having fixed tooltips on the same page as cursor tooltips, + // where both instances are referencing the same tooltip element, we + // need to keep track of the mouse position constantly, but we should + // only set the tip location if a fixed tip is not currently open, a tip + // open is imminent or active, and the tooltip element in question does + // have a mouse-follow using it. + if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) { + // grab measurements + var tipWidth = tipElement.outerWidth(), + tipHeight = tipElement.outerHeight(), + coords = new CSSCoordinates(), + collisions, + collisionCount; + + // grab collisions + coords.set('top', session.currentY + options.offset); + coords.set('left', session.currentX + options.offset); + collisions = getViewportCollisions( + coords, + tipWidth, + tipHeight + ); + + // handle tooltip view port collisions + if (collisions !== Collision.none) { + collisionCount = countFlags(collisions); + if (collisionCount === 1) { + // if there is only one collision (bottom or right) then + // simply constrain the tooltip to the view port + if (collisions === Collision.right) { + coords.set('left', session.windowWidth - tipWidth); + } else if (collisions === Collision.bottom) { + coords.set('top', session.scrollTop + session.windowHeight - tipHeight); + } + } else { + // if the tooltip has more than one collision then it is + // trapped in the corner and should be flipped to get it out + // of the users way + coords.set('left', session.currentX - tipWidth - options.offset); + coords.set('top', session.currentY - tipHeight - options.offset); + } + } + + // position the tooltip + tipElement.css(coords); + } + } + + /** + * Sets the tooltip to the correct position relative to the specified target + * element. Based on options settings. + * @private + * @param {jQuery} element The element that the tooltip should target. + */ + function positionTipOnElement(element) { + var priorityList, + finalPlacement; + + if (options.smartPlacement) { + priorityList = $.fn.powerTip.smartPlacementLists[options.placement]; + + // iterate over the priority list and use the first placement option + // that does not collide with the view port. if they all collide + // then the last placement in the list will be used. + $.each(priorityList, function(idx, pos) { + // place tooltip and find collisions + var collisions = getViewportCollisions( + placeTooltip(element, pos), + tipElement.outerWidth(), + tipElement.outerHeight() + ); + + // update the final placement variable + finalPlacement = pos; + + // break if there were no collisions + if (collisions === Collision.none) { + return false; + } + }); + } else { + // if we're not going to use the smart placement feature then just + // compute the coordinates and do it + placeTooltip(element, options.placement); + finalPlacement = options.placement; + } + + // add placement as class for CSS arrows + tipElement.removeClass('w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt'); + tipElement.addClass(finalPlacement); + } + + /** + * Sets the tooltip position to the appropriate values to show the tip at + * the specified placement. This function will iterate and test the tooltip + * to support elastic tooltips. + * @private + * @param {jQuery} element The element that the tooltip should target. + * @param {string} placement The placement for the tooltip. + * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and + * right position values. + */ + function placeTooltip(element, placement) { + var iterationCount = 0, + tipWidth, + tipHeight, + coords = new CSSCoordinates(); + + // set the tip to 0,0 to get the full expanded width + coords.set('top', 0); + coords.set('left', 0); + tipElement.css(coords); + + // to support elastic tooltips we need to check for a change in the + // rendered dimensions after the tooltip has been positioned + do { + // grab the current tip dimensions + tipWidth = tipElement.outerWidth(); + tipHeight = tipElement.outerHeight(); + + // get placement coordinates + coords = placementCalculator.compute( + element, + placement, + tipWidth, + tipHeight, + options.offset + ); + + // place the tooltip + tipElement.css(coords); + } while ( + // sanity check: limit to 5 iterations, and... + ++iterationCount <= 5 && + // try again if the dimensions changed after placement + (tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight()) + ); + + return coords; + } + + /** + * Checks for a tooltip desync and closes the tooltip if one occurs. + * @private + */ + function closeDesyncedTip() { + var isDesynced = false; + // It is possible for the mouse cursor to leave an element without + // firing the mouseleave or blur event. This most commonly happens when + // the element is disabled under mouse cursor. If this happens it will + // result in a desynced tooltip because the tooltip was never asked to + // close. So we should periodically check for a desync situation and + // close the tip if such a situation arises. + if (session.isTipOpen && !session.isClosing && !session.delayInProgress && ($.inArray('mouseleave', options.closeEvents) > -1 || $.inArray('mouseout', options.closeEvents) > -1 || $.inArray('blur', options.closeEvents) > -1 || $.inArray('focusout', options.closeEvents) > -1)) { + // user moused onto another tip or active hover is disabled + if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) { + isDesynced = true; + } else { + // hanging tip - have to test if mouse position is not over the + // active hover and not over a tooltip set to let the user + // interact with it. + // for keyboard navigation: this only counts if the element does + // not have focus. + // for tooltips opened via the api: we need to check if it has + // the forcedOpen flag. + if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) { + if (tipElement.data(DATA_MOUSEONTOTIP)) { + if (!isMouseOver(tipElement)) { + isDesynced = true; + } + } else { + isDesynced = true; + } + } + } + + if (isDesynced) { + // close the desynced tip + hideTip(session.activeHover); + } + } + } + + // expose methods + this.showTip = beginShowTip; + this.hideTip = hideTip; + this.resetPosition = positionTipOnElement; + } + + /** + * Determine whether a jQuery object is an SVG element + * @private + * @param {jQuery} element The element to check + * @return {boolean} Whether this is an SVG element + */ + function isSvgElement(element) { + return Boolean(window.SVGElement && element[0] instanceof SVGElement); + } + + /** + * Determines if the specified jQuery.Event object has mouse data. + * @private + * @param {jQuery.Event=} event The jQuery.Event object to test. + * @return {boolean} True if there is mouse data, otherwise false. + */ + function isMouseEvent(event) { + return Boolean(event && typeof event.pageX === 'number'); + } + + /** + * Initializes the viewport dimension cache and hooks up the mouse position + * tracking and viewport dimension tracking events. + * Prevents attaching the events more than once. + * @private + */ + function initTracking() { + if (!session.mouseTrackingActive) { + session.mouseTrackingActive = true; + + // grab the current viewport dimensions on load + getViewportDimensions(); + $(getViewportDimensions); + + // hook mouse move tracking + $document.on('mousemove' + EVENT_NAMESPACE, trackMouse); + + // hook viewport dimensions tracking + $window.on('resize' + EVENT_NAMESPACE, trackResize); + $window.on('scroll' + EVENT_NAMESPACE, trackScroll); + } + } + + /** + * Updates the viewport dimensions cache. + * @private + */ + function getViewportDimensions() { + session.scrollLeft = $window.scrollLeft(); + session.scrollTop = $window.scrollTop(); + session.windowWidth = $window.width(); + session.windowHeight = $window.height(); + } + + /** + * Updates the window size info in the viewport dimensions cache. + * @private + */ + function trackResize() { + session.windowWidth = $window.width(); + session.windowHeight = $window.height(); + } + + /** + * Updates the scroll offset info in the viewport dimensions cache. + * @private + */ + function trackScroll() { + var x = $window.scrollLeft(), + y = $window.scrollTop(); + if (x !== session.scrollLeft) { + session.currentX += x - session.scrollLeft; + session.scrollLeft = x; + } + if (y !== session.scrollTop) { + session.currentY += y - session.scrollTop; + session.scrollTop = y; + } + } + + /** + * Saves the current mouse coordinates to the session object. + * @private + * @param {jQuery.Event} event The mousemove event for the document. + */ + function trackMouse(event) { + session.currentX = event.pageX; + session.currentY = event.pageY; + } + + /** + * Tests if the mouse is currently over the specified element. + * @private + * @param {jQuery} element The element to check for hover. + * @return {boolean} + */ + function isMouseOver(element) { + // use getBoundingClientRect() because jQuery's width() and height() + // methods do not work with SVG elements + // compute width/height because those properties do not exist on the object + // returned by getBoundingClientRect() in older versions of IE + var elementPosition = element.offset(), + elementBox = element[0].getBoundingClientRect(), + elementWidth = elementBox.right - elementBox.left, + elementHeight = elementBox.bottom - elementBox.top; + + return session.currentX >= elementPosition.left && + session.currentX <= elementPosition.left + elementWidth && + session.currentY >= elementPosition.top && + session.currentY <= elementPosition.top + elementHeight; + } + + /** + * Fetches the tooltip content from the specified element's data attributes. + * @private + * @param {jQuery} element The element to get the tooltip content for. + * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or + * undefined if there was no tooltip content for the element. + */ + function getTooltipContent(element) { + var tipText = element.data(DATA_POWERTIP), + tipObject = element.data(DATA_POWERTIPJQ), + tipTarget = element.data(DATA_POWERTIPTARGET), + targetElement, + content; + + if (tipText) { + if ($.isFunction(tipText)) { + tipText = tipText.call(element[0]); + } + content = tipText; + } else if (tipObject) { + if ($.isFunction(tipObject)) { + tipObject = tipObject.call(element[0]); + } + if (tipObject.length > 0) { + content = tipObject.clone(true, true); + } + } else if (tipTarget) { + targetElement = $('#' + tipTarget); + if (targetElement.length > 0) { + content = targetElement.html(); + } + } + + return content; + } + + /** + * Finds any viewport collisions that an element (the tooltip) would have if it + * were absolutely positioned at the specified coordinates. + * @private + * @param {CSSCoordinates} coords Coordinates for the element. + * @param {number} elementWidth Width of the element in pixels. + * @param {number} elementHeight Height of the element in pixels. + * @return {number} Value with the collision flags. + */ + function getViewportCollisions(coords, elementWidth, elementHeight) { + var viewportTop = session.scrollTop, + viewportLeft = session.scrollLeft, + viewportBottom = viewportTop + session.windowHeight, + viewportRight = viewportLeft + session.windowWidth, + collisions = Collision.none; + + if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) { + collisions |= Collision.top; + } + if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) { + collisions |= Collision.bottom; + } + if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) { + collisions |= Collision.left; + } + if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) { + collisions |= Collision.right; + } + + return collisions; + } + + /** + * Counts the number of bits set on a flags value. + * @param {number} value The flags value. + * @return {number} The number of bits that have been set. + */ + function countFlags(value) { + var count = 0; + while (value) { + value &= value - 1; + count++; + } + return count; + } + +// return api for commonjs and amd environments + return $.powerTip; +})); diff --git a/i/jquery.powertip/jquery.powertip.min.js b/i/jquery.powertip/jquery.powertip.min.js new file mode 100644 index 0000000..9f20856 --- /dev/null +++ b/i/jquery.powertip/jquery.powertip.min.js @@ -0,0 +1,8 @@ +/*! + PowerTip v1.3.0 (2017-01-15) + https://stevenbenner.github.io/jquery-powertip/ + Copyright (c) 2017 Steven Benner (http://stevenbenner.com/). + Released under MIT license. + https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt +*/ +!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],b):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function(a){function b(){var b=this;b.top="auto",b.left="auto",b.right="auto",b.bottom="auto",b.set=function(c,d){a.isNumeric(d)&&(b[c]=Math.round(d))}}function c(a,b,c){function d(d,e){g(),a.data(u)?h():d?(e&&a.data(v,!0),i(),c.showTip(a)):(E.tipOpenImminent=!0,k=setTimeout(function(){k=null,f()},b.intentPollInterval))}function e(d){l&&(l=E.closeDelayTimeout=clearTimeout(l),E.delayInProgress=!1),g(),E.tipOpenImminent=!1,a.data(u)&&(a.data(v,!1),d?c.hideTip(a):(E.delayInProgress=!0,E.closeDelayTimeout=setTimeout(function(){E.closeDelayTimeout=null,c.hideTip(a),E.delayInProgress=!1,l=null},b.closeDelay),l=E.closeDelayTimeout))}function f(){var e=Math.abs(E.previousX-E.currentX),f=Math.abs(E.previousY-E.currentY),g=e+f;g-1||a.inArray("mouseout",c.closeEvents)>-1||a.inArray("blur",c.closeEvents)>-1||a.inArray("focusout",c.closeEvents)>-1)&&(E.activeHover.data(u)===!1||E.activeHover.is(":disabled")?b=!0:m(E.activeHover)||E.activeHover.is(":focus")||E.activeHover.data(v)||(y.data(x)?m(y)||(b=!0):b=!0),b&&g(E.activeHover))}var l=new d,y=a("#"+c.popupId);0===y.length&&(y=a("
",{id:c.popupId}),0===s.length&&(s=a("body")),s.append(y),E.tooltips=E.tooltips?E.tooltips.add(y):y),c.followMouse&&(y.data(w)||(q.on("mousemove"+C,h),r.on("scroll"+C,h),y.data(w,!0))),this.showTip=e,this.hideTip=g,this.resetPosition=i}function f(a){return Boolean(window.SVGElement&&a[0]instanceof SVGElement)}function g(a){return Boolean(a&&"number"==typeof a.pageX)}function h(){E.mouseTrackingActive||(E.mouseTrackingActive=!0,i(),a(i),q.on("mousemove"+C,l),r.on("resize"+C,j),r.on("scroll"+C,k))}function i(){E.scrollLeft=r.scrollLeft(),E.scrollTop=r.scrollTop(),E.windowWidth=r.width(),E.windowHeight=r.height()}function j(){E.windowWidth=r.width(),E.windowHeight=r.height()}function k(){var a=r.scrollLeft(),b=r.scrollTop();a!==E.scrollLeft&&(E.currentX+=a-E.scrollLeft,E.scrollLeft=a),b!==E.scrollTop&&(E.currentY+=b-E.scrollTop,E.scrollTop=b)}function l(a){E.currentX=a.pageX,E.currentY=a.pageY}function m(a){var b=a.offset(),c=a[0].getBoundingClientRect(),d=c.right-c.left,e=c.bottom-c.top;return E.currentX>=b.left&&E.currentX<=b.left+d&&E.currentY>=b.top&&E.currentY<=b.top+e}function n(b){var c,d,e=b.data(z),f=b.data(A),g=b.data(B);return e?(a.isFunction(e)&&(e=e.call(b[0])),d=e):f?(a.isFunction(f)&&(f=f.call(b[0])),f.length>0&&(d=f.clone(!0,!0))):g&&(c=a("#"+g),c.length>0&&(d=c.html())),d}function o(a,b,c){var d=E.scrollTop,e=E.scrollLeft,f=d+E.windowHeight,g=e+E.windowWidth,h=F.none;return(a.topf||Math.abs(a.bottom-E.windowHeight)>f)&&(h|=F.bottom),(a.leftg)&&(h|=F.left),(a.left+b>g||a.right-1?j.on(c+C,function(b){a.powerTip.toggle(this,b)}):j.on(c+C,function(b){a.powerTip.show(this,b)})}),a.each(f.closeEvents,function(b,c){a.inArray(c,f.openEvents)<0&&j.on(c+C,function(b){a.powerTip.hide(this,!g(b))})}),j.on("keydown"+C,function(b){27===b.keyCode&&a.powerTip.hide(this,!0)})),E.elements=E.elements?E.elements.add(j):j,j):j},a.fn.powerTip.defaults={fadeInTime:200,fadeOutTime:100,followMouse:!1,popupId:"powerTip",popupClass:null,intentSensitivity:7,intentPollInterval:100,closeDelay:100,placement:"n",smartPlacement:!1,offset:10,mouseOnToPopup:!1,manual:!1,openEvents:["mouseenter","focus"],closeEvents:["mouseleave","blur"]},a.fn.powerTip.smartPlacementLists={n:["n","ne","nw","s"],e:["e","ne","se","w","nw","sw","n","s","e"],s:["s","se","sw","n"],w:["w","nw","sw","e","ne","se","n","s","w"],nw:["nw","w","sw","n","s","se","nw"],ne:["ne","e","se","n","s","sw","ne"],sw:["sw","w","nw","s","n","ne","sw"],se:["se","e","ne","s","n","nw","se"],"nw-alt":["nw-alt","n","ne-alt","sw-alt","s","se-alt","w","e"],"ne-alt":["ne-alt","n","nw-alt","se-alt","s","sw-alt","e","w"],"sw-alt":["sw-alt","s","se-alt","nw-alt","n","ne-alt","w","e"],"se-alt":["se-alt","s","sw-alt","ne-alt","n","nw-alt","e","w"]},a.powerTip={show:function(b,c){return g(c)?(l(c),E.previousX=c.pageX,E.previousY=c.pageY,a(b).data(t).show()):a(b).first().data(t).show(!0,!0),b},reposition:function(b){return a(b).first().data(t).resetPosition(),b},hide:function(b,c){var d;return c=!b||c,b?d=a(b).first().data(t):E.activeHover&&(d=E.activeHover.data(t)),d&&d.hide(c),b},toggle:function(b,c){return E.activeHover&&E.activeHover.is(b)?a.powerTip.hide(b,!g(c)):a.powerTip.show(b,c),b},destroy:function(b){var c=b?a(b):E.elements;return E.elements&&0!==E.elements.length?(c.off(C).each(function(){var b=a(this),c=[y,t,u,v];b.data(y)&&(b.attr("title",b.data(y)),c.push(z)),b.removeData(c)}),E.elements=E.elements.not(c),0===E.elements.length&&(r.off(C),q.off(C),E.mouseTrackingActive=!1,E.tooltips.remove(),E.tooltips=null),b):b}},a.powerTip.showTip=a.powerTip.show,a.powerTip.closeTip=a.powerTip.hide,a.powerTip}); \ No newline at end of file diff --git a/i/main.js b/i/main.js index 1788500..1d5fc4a 100644 --- a/i/main.js +++ b/i/main.js @@ -1,3 +1,8 @@ +/* +TODO: Auto-reconnect web sockets +TODO: "Click" the "Jog Control" tab when the config reloads (just in case) +*/ + /* GRBLWeb - a web based CNC controller for GRBL @@ -25,178 +30,193 @@ */ -$(document).ready(function() { - - // init vars for better controls - var isMouseDown = false; - var tsLast = Date.now(); - - // get dimensions for better controls - var betterWidth = $('#betterControls').width(); - var betterHeight = $('#betterControls').height(); - var bPointWidth = $('#betterControlsPoint').width(); - var bPointHeight = $('#betterControlsPoint').height(); - - // track the control mouse position externally - var betterX = 0; - var betterY = 0; - - // get the scale factor to reduce x and y to a resolution of -1 to 1 - var xSf = 2/betterWidth; - var ySf = 2/betterHeight; - - // center the control point - $('#betterControlsPoint').css('top', (betterHeight/2)-(bPointHeight/2) + 'px'); - $('#betterControlsPoint').css('left', (betterWidth/2)-(bPointWidth/2) + 'px'); - - // on mousedown, set isMouseDown to true - $('#betterControls').mousedown(function(event) { - event.preventDefault(); - isMouseDown = true; - }); - document.getElementById('betterControls').addEventListener('touchstart', function(event) { - event.preventDefault(); - isMouseDown = true; - }, false); - - // on mouseup reset center point - $('#betterControls').mouseup(function(event) { - event.preventDefault(); - isMouseDown = false; - $('#betterControlsPoint').css('top', (betterHeight/2)-(bPointHeight/2) + 'px'); - $('#betterControlsPoint').css('left', (betterWidth/2)-(bPointWidth/2) + 'px'); - }); - document.getElementById('betterControls').addEventListener('touchend', function(event) { - event.preventDefault(); - isMouseDown = false; - $('#betterControlsPoint').css('top', (betterHeight/2)-(bPointHeight/2) + 'px'); - $('#betterControlsPoint').css('left', (betterWidth/2)-(bPointWidth/2) + 'px'); - }); - - // loop for bettercontrol - setInterval(function() { - if (isMouseDown) { - - var gcX = betterX-(betterWidth/2); - var gcY = betterY-(betterHeight/2); - - // add the scale factors - gcX = xSf*gcX; - gcY = ySf*gcY; - - // invert y axis because JS and CNC are opposite there - if (gcY < 0) { - gcY = Math.abs(gcY); - } else if (gcY > 0) { - gcY = -gcY; - } - - // first get speed, calculated from the mean of abs(x) and abs(y) - //var fSpeed = (Math.abs(gcX)+Math.abs(gcY))/2; - - // first get speed, calculated from the highest abs of x and y - if (Math.abs(gcX) > Math.abs(gcY)) { - var fSpeed = Math.abs(gcX)*$('#jogSpeed').val(); - } else { - var fSpeed = Math.abs(gcY)*$('#jogSpeed').val(); - } - - fSpeed = Math.round(fSpeed*100)/100; - - // set final position movements based on #jogSize - gcX = Math.round(gcX*$('#jogSize').val()*1000)/1000; - gcY = Math.round(gcY*$('#jogSize').val()*1000)/1000; - - // gcode to send - socket.emit('gcodeLine', { line: 'G91\nG0 F'+fSpeed+' X'+gcX+' Y'+gcY+'\nG90\n'}); - } - }, 200); - - // on mousemove send gcode - $('#betterControls').mousemove(function(event) { - if (isMouseDown) { - betterX = event.pageX-this.offsetLeft; - betterY = event.pageY-this.offsetTop; - - // move point - $('#betterControlsPoint').css('top',betterY-(bPointHeight/2) + 'px'); - $('#betterControlsPoint').css('left',betterX-(bPointWidth/2) + 'px'); - - } - }); - document.getElementById('betterControls').addEventListener('touchmove', function(event) { - event.preventDefault(); - if (isMouseDown) { - betterX = event.pageX-this.offsetLeft; - betterY = event.pageY-this.offsetTop; - - // move point - $('#betterControlsPoint').css('top',betterY-(bPointHeight/2) + 'px'); - $('#betterControlsPoint').css('left',betterX-(bPointWidth/2) + 'px'); - - } - }); +var lastUnitsOfMeasurement = ''; +var unitsBeforeProbe = ''; +var config = {}; +$(document).ready(function() { $( window ).resize(function() { // when header resizes, move ui down $('.table-layout').css('margin-top',$('.navbar-collapse').height()-34); }); + // Set up powertip and optional location + $('[data-powertip]').each(function() { + var opts = {}; + + if($(this).data('powertippos')!=undefined) + opts.placement = $(this).data('powertippos'); + + $(this).powerTip(opts); + }); + var socket = io.connect(''); socket.on('serverError', function (data) { - alert(data); + console.log(data); }); + socket.on('gcodeFromJscut', function (data) { $('#command').val(data.val); openGCodeFromText(); alert('new data from jscut'); }); + // config from server socket.on('config', function (data) { + console.log('config', data); + config = data; + if (data.showWebCam == true) { // show the webcam and link - var webroot = window.location.protocol+'//'+window.location.hostname; - //console.log(webroot); $('#wcImg').attr('src', webroot+':'+data.webcamPort+'/?action=stream'); - $('#wcLink').attr('href', webroot+':'+data.webcamPort+'/javascript_simple.html'); - $('#webcam').css('display','inline-block'); } + + // Hide the jsCut button if jsCut is "disabled" + if(data.enableJsCut==0) { + $('#jsCutButton').hide(); + } + + // Show controls tabs to "enable" probe controls + if(data.enableProbeControls==1) { + $('#controlTabs').show(); + } + + // Hide "better controls" and show simple controls + if(data.enableSimpleControls==1) { + $('#betterControlsWrapper').hide(); + $('#simpleControls').show(); + $('#probeControls').hide(); + } + + // Fill in jog default step increment + if(data.jogControlDefaultIncr!=undefined) + $('#jogSize').val(data.jogControlDefaultIncr).attr('placeholder', data.jogControlDefaultIncr); + + // Fill in jog default feed rate + if(data.jogControlDefaultFeed!=undefined) + $('#jogSpeed').val(data.jogControlDefaultFeed).attr('placeholder', data.jogControlDefaultFeed); + + // Fill in default X offset for probe + if(data.probeControlXOffset!=undefined) + $('#probeOffsetX').val(data.probeControlXOffset).attr('placeholder', data.probeControlXOffset); + + // Fill in default Y offset for probe + if(data.probeControlYOffset!=undefined) + $('#probeOffsetY').val(data.probeControlYOffset).attr('placeholder', data.probeControlYOffset); + + // Fill in default Z offset for probe + if(data.probeControlZOffset!=undefined) + $('#probeOffsetZ').val(data.probeControlZOffset).attr('placeholder', data.probeControlZOffset); + + // "Click" the "Jog Control" tab + $('#controlTabLink').click().parent().click(); }); + socket.on('ports', function (data) { - //console.log('ports event',data); $('#choosePort').html(''); for (var i=0; i'+data[i].comName+':'+data[i].pnpId+''); + var selected = ''; + + if(data[i].pnpId!=undefined && data[i].pnpId.toUpperCase().indexOf("ARDUINO_UNO")>=0) + selected = 'selected="selected"'; + + $('#choosePort').append(''); } if (data.length == 1) { $('#choosePort').val('0'); $('#choosePort').change(); } + + $('#choosePort').change(); }); + socket.on('qStatus', function (data) { $('#qStatus').html(data.currentLength+'/'+data.currentMax); + + var pct = Math.round((data.currentMax-data.currentLength)/data.currentMax*100); + if(isNaN(pct)) + pct = 100; + + if(data.currentMax==0) + pct = 0; + + $('#queueProgress').attr('aria-valuenow', pct).width(pct+'%').text(pct+'%'); + + if(pct>0) + $('#queueProgress').addClass('active'); + else + $('#queueProgress').removeClass('active'); }); + socket.on('machineStatus', function (data) { + if(data.status.toUpperCase()=='ALARM') { + data.status = ''+data.status+''; + } + + // $('#console').append('

'+JSON.stringify(data)+'

'); + // data.status = ''+data+''; + $('#mStatus').html(data.status); + + // Convert machine coordinates from mm to inches + if(data.unitsOfMeasurement=='in') { + for(var i in data) { + if(i.indexOf("pos")>=0) { + for(var j in data[i]) { + data[i][j] = (Math.round(parseFloat(data[i][j]/25.4) * 10000)/10000);; + } + } + } + } + $('#mX').html('X: '+data.mpos[0]); $('#mY').html('Y: '+data.mpos[1]); $('#mZ').html('Z: '+data.mpos[2]); $('#wX').html('X: '+data.wpos[0]); $('#wY').html('Y: '+data.wpos[1]); $('#wZ').html('Z: '+data.wpos[2]); - //console.log(data); + + // Only attempt to change things if the unit of measurement has changed + if(data.unitsOfMeasurement!=lastUnitsOfMeasurement) { + if (config.showUnitsBackdrop) + $('.unitsOfMeasurementText').text(data.unitsOfMeasurement); + + // Values for jog options + var step_incr = parseFloat($('#jogSize').val()); + var step_feed = parseFloat($('#jogSpeed').val()); + + if(data.unitsOfMeasurement.toUpperCase()=='IN') { + $('#setInches').addClass('btn-primary'); + $('#setMillimeters').removeClass('btn-primary'); + + $('#jogSize').val(step_incr/25.4); + $('#jogSpeed').val(step_feed/25.4); + } else if(data.unitsOfMeasurement.toUpperCase()=='MM') { + $('#setInches').removeClass('btn-primary'); + $('#setMillimeters').addClass('btn-primary'); + + // Don't do any conversion if this is the first page load + if(lastUnitsOfMeasurement!='') { + $('#jogSize').val(step_incr*25.4); + $('#jogSpeed').val(step_feed*25.4); + } + } + + lastUnitsOfMeasurement = data.unitsOfMeasurement; + } }); + socket.on('serialRead', function (data) { if ($('#console p').length > 300) { // remove oldest if already at 300 lines @@ -206,6 +226,28 @@ $(document).ready(function() { $('#console').scrollTop($("#console")[0].scrollHeight - $("#console").height()); }); + + socket.on('sensors', function(data) { + if(config.enablePiTemperature==1) { + var tempHtml = ''; + + for(var i in data) { + if(config.piTemperatureFahrenheit==1) { + tempHtml += Math.round(parseFloat(data[i]) * 9/5 + 32)+'°F, '; + } else { + tempHtml += (Math.round(parseFloat(data[i]) * 100)/100) +'°C, '; + } + } + + // Cut off the last ", " + tempHtml = tempHtml.trim(); + tempHtml = tempHtml.substr(0, tempHtml.length-7); + + $('#mTemp').html(tempHtml); + } + }) + + $('#choosePort').on('change', function() { // select port socket.emit('usePort', $('#choosePort').val()); @@ -258,15 +300,140 @@ $(document).ready(function() { $('#mPosition').hide(); }); + $('#probeTabLink').on('click', function() { + $('#probeTab').addClass('active'); + $('#controlTab').removeClass('active'); + $('#probeControls').show(); + $('#simpleControls').hide(); + }); + + $('#controlTabLink').on('click', function() { + $('#controlTab').addClass('active'); + $('#probeTab').removeClass('active'); + $('#simpleControls').show(); + $('#probeControls').hide(); + }); + + $('#probeHomeZero').on('click', function() { + socket.emit('gcodeLine', { line: '$H' }); + socket.emit('gcodeLine', { line: 'G92 X0 Y0 Z0' }); + }); + + $('#probeX').on('click', function() { + var offsetX = parseFloat($('#probeOffsetX').val())+parseFloat($('#probeBitDiameter').val()/2); + + // Remember the units of measurement before we start the probe + unitsBeforeProbe = lastUnitsOfMeasurement; + + socket.emit('gcodeLine', { line: 'G21' }); // Set to mm + socket.emit('gcodeLine', { line: 'G38.2 X-50 F20' }); // Probe X + socket.emit('gcodeLine', { line: 'G92 X'+(offsetX*-1) }); // Set X offset + socket.emit('gcodeLine', { line: 'G1 X'+(offsetX+2)+' F1000' }); // Move probe 2mm away from offset + + // Restore the units of measurement after the probe + if(unitsBeforeProbe=='in') + socket.emit('gcodeLine', { line: 'G20' }); + }); + + $('#probeY').on('click', function() { + var offsetY = parseFloat($('#probeOffsetY').val())+parseFloat($('#probeBitDiameter').val()/2); + + // Remember the units of measurement before we start the probe + unitsBeforeProbe = lastUnitsOfMeasurement; + + socket.emit('gcodeLine', { line: 'G21' }); // Set to mm + socket.emit('gcodeLine', { line: 'G38.2 Y-50 F20' }); // Probe Y + socket.emit('gcodeLine', { line: 'G92 Y'+(offsetY*-1) }); // Set Y offset + socket.emit('gcodeLine', { line: 'G1 Y'+(offsetY+2)+' F1000' }); // Move probe 2mm away from offset + + // Restore the units of measurement after the probe + if(unitsBeforeProbe=='in') + socket.emit('gcodeLine', { line: 'G20' }); + }); + + $('#probeZ').on('click', function() { + var offsetZ = parseFloat($('#probeOffsetZ').val()); + + // Remember the units of measurement before we start the probe + unitsBeforeProbe = lastUnitsOfMeasurement; + + socket.emit('gcodeLine', { line: 'G21' }); // Set to mm + socket.emit('gcodeLine', { line: 'G38.2 Z-75 F20'}); // Probe Z + socket.emit('gcodeLine', { line: 'G92 Z'+offsetZ }); // Set Z offset + socket.emit('gcodeLine', { line: 'G1 Z'+(offsetZ+5)+' F1000' }); // Move probe 5mm away from offset + + // Restore the units of measurement after the probe + if(unitsBeforeProbe=='in') + socket.emit('gcodeLine', { line: 'G20' }); + }); + + $('#probeAll').on('click', function() { + var offsetX = parseFloat($('#probeOffsetX').val())+parseFloat($('#probeBitDiameter').val()/2)+10; + var offsetY = parseFloat($('#probeOffsetY').val())+parseFloat($('#probeBitDiameter').val()/2)+10; + var offsetZ = parseFloat($('#probeOffsetZ').val())+10; + + $('#probeZ').click(); // Probe Z + $('#probeX').click(); // Probe X + $('#probeY').click(); // Probe Y + + // Remember the units of measurement before we start the probe + unitsBeforeProbe = lastUnitsOfMeasurement; + + socket.emit('gcodeLine', { line: 'G21' }); + socket.emit('gcodeLine', { line: 'G1 X'+offsetX+' Y'+offsetY+' Z'+offsetZ+' F1000' }); // Move probe away from offset + + // Restore the units of measurement after the probe + if(unitsBeforeProbe=='in') + socket.emit('gcodeLine', { line: 'G20' }); + }); + + $('#gotoZeroZeroZero').on('click', function() { + socket.emit('gcodeLine', { line: 'G1 X0 Y0 Z0 F500' }); + }); + $('#sendZero').on('click', function() { socket.emit('gcodeLine', { line: 'G92 X0 Y0 Z0' }); + socket.emit('gcodeLine', { line: 'G28.1' }); }); - $('#sendCommand').on('click', function() { + $('#sendZeroX').on('click', function() { + socket.emit('gcodeLine', { line: 'G92 X0' }); + socket.emit('gcodeLine', { line: 'G28.1 X0' }); + }); + + $('#sendZeroY').on('click', function() { + socket.emit('gcodeLine', { line: 'G92 Y0' }); + socket.emit('gcodeLine', { line: 'G28.1 Y0' }); + }); + $('#sendZeroZ').on('click', function() { + socket.emit('gcodeLine', { line: 'G92 Z0' }); + socket.emit('gcodeLine', { line: 'G28.1 Z0' }); + }); + + $('#setInches').on('click', function() { + socket.emit('gcodeLine', { line: 'G20' }); + }); + + $('#setMillimeters').on('click', function() { + socket.emit('gcodeLine', { line: 'G21' }); + }); + + $('#sendHome').on('click', function() { + socket.emit('gcodeLine', { line: '$H' }); + }); + + $('#sendUnlock').on('click', function() { + socket.emit('gcodeLine', { line: '$X' }); + }); + + $('#sendCommand').on('click', function() { socket.emit('gcodeLine', { line: $('#command').val() }); $('#command').val(''); + }); + $('#refreshPorts').on('click', function() { + socket.emit('refreshPorts', {}); }); // shift enter for send command @@ -285,73 +452,107 @@ $(document).ready(function() { $('#xM').on('click', function() { socket.emit('gcodeLine', { line: 'G91\nG1 F'+$('#jogSpeed').val()+' X-'+$('#jogSize').val()+'\nG90'}); }); + $('#xP').on('click', function() { socket.emit('gcodeLine', { line: 'G91\nG1 F'+$('#jogSpeed').val()+' X'+$('#jogSize').val()+'\nG90'}); }); + $('#yP').on('click', function() { socket.emit('gcodeLine', { line: 'G91\nG1 F'+$('#jogSpeed').val()+' Y'+$('#jogSize').val()+'\nG90'}); }); + $('#yM').on('click', function() { socket.emit('gcodeLine', { line: 'G91\nG1 F'+$('#jogSpeed').val()+' Y-'+$('#jogSize').val()+'\nG90'}); }); + $('#zP').on('click', function() { - socket.emit('gcodeLine', { line: 'G91\nG1 F'+$('#jogSpeed').val()+' Z'+$('#jogSize').val()+'\nG90'}); + var distance = parseFloat($('#jogSize').val()); + + if(lastUnitsOfMeasurement=='in') + distance = distance*25.4 + + if(distance>=config.maxMoveZ) + distance = config.maxMoveZ; + + if(lastUnitsOfMeasurement=='in') + distance = distance/25.4; + + // Throw a warning in the console + $('#console').append('

!!!!: Only moved '+distance+lastUnitsOfMeasurement+' due to maxMoveZ safety limit

'); + $('#console').scrollTop($("#console")[0].scrollHeight - $("#console").height()); + + socket.emit('gcodeLine', { line: 'G91\nG1 F'+$('#jogSpeed').val()+' Z'+distance+'\nG90'}); }); + $('#zM').on('click', function() { - socket.emit('gcodeLine', { line: 'G91\nG1 F'+$('#jogSpeed').val()+' Z-'+$('#jogSize').val()+'\nG90'}); - }); + var distance = parseFloat($('#jogSize').val()); - // WASD and up/down keys - $(document).keydown(function (e) { - var keyCode = e.keyCode || e.which; + if(lastUnitsOfMeasurement=='in') + distance = distance*25.4 - if ($('#command').is(':focus')) { - // don't handle keycodes inside command window - return; - } + if(distance>=config.maxMoveZ) + distance = config.maxMoveZ; - switch (keyCode) { - case 65: - // a key X- - e.preventDefault(); - $('#xM').click(); - break; - case 68: - // d key X+ - e.preventDefault(); - $('#xP').click(); - break; - case 87: - // w key Y+ - e.preventDefault(); - $('#yP').click(); - break; - case 83: - // s key Y- - e.preventDefault(); - $('#yM').click(); - break; - case 38: - // up arrow Z+ - e.preventDefault(); - $('#zP').click(); - break; - case 40: - // down arrow Z- - e.preventDefault(); - $('#zM').click(); - break; - } + if(lastUnitsOfMeasurement=='in') + distance = distance/25.4; + + // Throw a warning in the console + $('#console').append('

!!!!: Only moved '+distance+lastUnitsOfMeasurement+' due to maxMoveZ safety limit

'); + $('#console').scrollTop($("#console")[0].scrollHeight - $("#console").height()); + + socket.emit('gcodeLine', { line: 'G91\nG1 F'+$('#jogSpeed').val()+' Z-'+distance+'\nG90'}); }); + // WASD and up/down keys + // $(document).keydown(function (e) { + // var keyCode = e.keyCode || e.which; + // + // if ($('#command').is(':focus')) { + // // don't handle keycodes inside command window + // return; + // } + // + // switch (keyCode) { + // case 65: + // // a key X- + // e.preventDefault(); + // $('#xM').click(); + // break; + // case 68: + // // d key X+ + // e.preventDefault(); + // $('#xP').click(); + // break; + // case 87: + // // w key Y+ + // e.preventDefault(); + // $('#yP').click(); + // break; + // case 83: + // // s key Y- + // e.preventDefault(); + // $('#yM').click(); + // break; + // case 38: + // // up arrow Z+ + // e.preventDefault(); + // $('#zP').click(); + // break; + // case 40: + // // down arrow Z- + // e.preventDefault(); + // $('#zM').click(); + // break; + // } + // }); + // handle gcode uploads if (window.FileReader) { - var reader = new FileReader (); // drag and drop function dragEvent (ev) { - ev.stopPropagation (); + ev.stopPropagation (); ev.preventDefault (); if (ev.type == 'drop') { reader.onloadend = function (ev) { @@ -359,7 +560,7 @@ $(document).ready(function() { openGCodeFromText(); }; reader.readAsText (ev.dataTransfer.files[0]); - } + } } document.getElementById('command').addEventListener ('dragenter', dragEvent, false); @@ -379,5 +580,4 @@ $(document).ready(function() { } else { alert('your browser is too old to upload files, get the latest Chromium or Firefox'); } - }); diff --git a/server.js b/server.js index c7aca3f..1021bb4 100644 --- a/server.js +++ b/server.js @@ -1,33 +1,39 @@ +/* + TODO: Allow saving/loading of probing profiles +*/ + /* - GRBLWeb - a web based CNC controller for GRBL - Copyright (C) 2015 Andrew Hodel + GRBLWeb - a web based CNC controller for GRBL + Copyright (C) 2015 Andrew Hodel - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License. - 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 Affero General Public License for more details. + 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 Affero General Public License for more details. - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +//var reload = require('require-reload')(require); +//var config = reload('./config.js'); var config = require('./config'); var serialport = require("serialport"); -var SerialPort = serialport.SerialPort; // localize object constructor +var SerialPort = serialport.SerialPort; var app = require('http').createServer(handler) , io = require('socket.io').listen(app) , fs = require('fs'); @@ -40,16 +46,31 @@ var http = require('http'); // test for webcam config.showWebCam = false; -http.get('http://127.0.0.1:8080', function(res) { + +// Monitor config.js for changes +/* +fs.watch('./config.js', function(e, f) { + console.log('config.js changed, reloading'); + console.log('config: '+JSON.stringify(config)); + + config = reload('./config.js'); + + for(var i in io.sockets.connected) + io.sockets.connected[i].emit('config', config); +}); +*/ + + +http.get('http://127.0.0.1:8080/?action=snapshot', function(res) { // valid response, enable webcam console.log('enabling webcam'); config.showWebCam = true; }).on('socket', function(socket) { // 2 second timeout on this socket socket.setTimeout(2000); - socket.on('timeout', function() { - this.abort(); - }); + //socket.on('timeout', function() { + // this.abort(); + //}); }).on('error', function(e) { console.log('Got error: '+e.message+' not enabling webcam') }); @@ -57,10 +78,8 @@ http.get('http://127.0.0.1:8080', function(res) { app.listen(config.webPort); var fileServer = new static.Server('./i'); -function handler (req, res) { - - //console.log(req.url); +function handler (req, res) { if (req.url.indexOf('/api/uploadGcode') == 0 && req.method == 'POST') { // this is a gcode upload, probably from jscut console.log('new data from jscut'); @@ -80,67 +99,78 @@ function handler (req, res) { }); } else { fileServer.serve(req, res, function (err, result) { - if (err) console.log('fileServer error: ',err); + if (err) console.log('fileServer error: ', err, req.url); }); } } + function ConvChar( str ) { c = {'<':'<', '>':'>', '&':'&', '"':'"', "'":''', - '#':'#' }; + '#':'#' }; return str.replace( /[<&>'"#]/g, function(s) { return c[s]; } ); } + var sp = []; var allPorts = []; +var piTemp = []; + +function doSerialPortList() { + serialport.list(function (err, ports) { + // if on rPi - http://www.hobbytronics.co.uk/raspberry-pi-serial-port + if (fs.existsSync('/dev/ttyAMA0') && config.usettyAMA0 == 1) { + (ports = ports || []).push({comName:'/dev/ttyAMA0',manufacturer: undefined,pnpId: 'raspberryPi__GPIO'}); + console.log('adding /dev/ttyAMA0 because it is enabled in config.js, you may need to enable it in the os - http://www.hobbytronics.co.uk/raspberry-pi-serial-port'); + } -serialport.list(function (err, ports) { - - // if on rPi - http://www.hobbytronics.co.uk/raspberry-pi-serial-port - if (fs.existsSync('/dev/ttyAMA0') && config.usettyAMA0 == 1) { - (ports = ports || []).push({comName:'/dev/ttyAMA0',manufacturer: undefined,pnpId: 'raspberryPi__GPIO'}); - console.log('adding /dev/ttyAMA0 because it is enabled in config.js, you may need to enable it in the os - http://www.hobbytronics.co.uk/raspberry-pi-serial-port'); - } + allPorts = ports; + + for (var i=0; iRESP: '+data+''}); @@ -191,7 +237,6 @@ function serialData(data, port) { sp[port].lastSerialWrite.shift(); } else if (data.indexOf('error') == 0) { - // error is red emitToPortSockets(port, 'serialRead', {'line':'RESP: '+data+''}); @@ -208,6 +253,14 @@ function serialData(data, port) { } else { // other is grey emitToPortSockets(port, 'serialRead', {'line':'RESP: '+data+''}); + + // This is where we're likely to see the units of measurement response + // Inches + if(data.indexOf(' G20 ')>=0) + unitsOfMeasurement = 'in'; + + if(data.indexOf(' G21 ')>=0) + unitsOfMeasurement = 'mm'; } if (sp[port].q.length == 0) { @@ -222,14 +275,16 @@ function serialData(data, port) { } + var currentSocketPort = {}; -function sendFirstQ(port) { +function sendFirstQ(port) { if (sp[port].q.length < 1) { // nothing to send return; } + var t = sp[port].q.shift(); // remove any comments after the command @@ -242,7 +297,8 @@ function sendFirstQ(port) { sendFirstQ(port); return; } - //console.log('sending '+t+' ### '+sp[port].q.length+' current q length'); + + // console.log('sending '+t+' ### '+sp[port].q.length+' current q length'); // loop through all registered port clients for (var i=0; i=0 || data.line.toUpperCase().indexOf("G21")>=0 || data.line.toUpperCase().indexOf("$X")>=0) + data.line += "\n$G"; // valid serial port selected, safe to send // split newlines @@ -289,7 +355,6 @@ io.sockets.on('connection', function (socket) { } else { socket.emit('serverError', 'you must select a serial port'); } - }); socket.on('clearQ', function(data) { @@ -312,7 +377,6 @@ io.sockets.on('connection', function (socket) { }); socket.on('disconnect', function() { - if (typeof currentSocketPort[socket.id] != 'undefined') { for (var c=0; c=0 && data.indexOf('YES')>=0) { + piTemp[i] = parseFloat(data.substr(data.indexOf('t=')+2).trim())/1000; + + // Maybe the internal temp + } else + piTemp[i] = parseFloat(data)/1000; + } + + emitToAllPortSockets('sensors', piTemp); + + setTimeout(getSensors, 2000); +}