diff --git a/.travis.yml b/.travis.yml index aab29ee..4b2afb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: c sudo: required -dist: precise +dist: trusty compiler: - gcc diff --git a/CMakeLists.txt b/CMakeLists.txt index cce24fe..fb4945d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,8 @@ include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_I include(CheckCSourceCompiles) -set(CMAKE_C_FLAGS "-std=gnu99 -Wall") -set(CMAKE_C_FLAGS_DEBUG "-ggdb -pedantic") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -pedantic") if(WITH_IPV6) set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_IPV6) endif() diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c89b804 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM alpine:3.5 +WORKDIR /app/build +COPY . /app +RUN apk add --no-cache g++ make cmake libmpdclient-dev openssl-dev +RUN cmake .. +RUN make + +FROM alpine:3.5 +RUN apk add --no-cache libmpdclient openssl +EXPOSE 8080 +COPY --from=0 /app/build/ympd /usr/bin/ympd +COPY --from=0 /app/build/mkdata /usr/bin/mkdata +CMD ympd \ No newline at end of file diff --git a/README.md b/README.md index 08ef76d..bc14cb8 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,12 @@ Dependencies ------------ - libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/ - cmake 2.6: http://cmake.org/ + - OpenSSL: https://www.openssl.org/ Unix Build Instructions ----------------------- -1. install dependencies, cmake and libmpdclient are available from all major distributions. +1. install dependencies. cmake, libmpdclient (dev), and OpenSSL (dev) are available from all major distributions. 2. create build directory ```cd /path/to/src; mkdir build; cd build``` 3. create makefile ```cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr``` 4. build ```make``` @@ -28,12 +29,13 @@ Run flags ``` Usage: ./ympd [OPTION]... - -h, --host connect to mpd at host [localhost] - -p, --port connect to mpd at port [6600] - -w, --webport [ip:] listen interface/port for webserver [8080] - -u, --user drop priviliges to user after socket bind - -V, --version get version - --help this help + -h, --host connect to mpd at host [localhost] + -p, --port connect to mpd at port [6600] + -w, --webport [ip:] listen interface/port for webserver [8080] + -d, --dirbletoken Dirble API token + -u, --user drop priviliges to user after socket bind + -V, --version get version + --help this help ``` SSL Support diff --git a/contrib/init.debian b/contrib/init.debian index 5c10bdf..943e4de 100755 --- a/contrib/init.debian +++ b/contrib/init.debian @@ -24,6 +24,7 @@ YMPD_USER=nobody MPD_HOST=localhost MPD_PORT=6600 WEB_PORT=8080 +DIRBLE_API_TOKEN=2e223c9909593b94fc6577361a # Exit if the package is not installed @@ -35,7 +36,7 @@ WEB_PORT=8080 # Load the VERBOSE setting and other rcS variables [ -f /etc/default/rcS ] && . /etc/default/rcS -DAEMON_OPT="--user $YMPD_USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT" +DAEMON_OPT="--user $YMPD_USER --mpdpass '$MPD_PASSWORD' --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT --dirbletoken $DIRBLE_API_TOKEN" do_start() { diff --git a/contrib/ympd.default b/contrib/ympd.default index c7bbcda..cb3ca72 100644 --- a/contrib/ympd.default +++ b/contrib/ympd.default @@ -1,4 +1,6 @@ MPD_HOST=localhost MPD_PORT=6600 +MPD_PASSWORD= WEB_PORT=8080 YMPD_USER=nobody +DIRBLE_API_TOKEN=2e223c9909593b94fc6577361a diff --git a/contrib/ympd.freebsd b/contrib/ympd.freebsd new file mode 100755 index 0000000..4dcbedd --- /dev/null +++ b/contrib/ympd.freebsd @@ -0,0 +1,29 @@ +#!/bin/sh + +# PROVIDE: ympd +# REQUIRE: DAEMON musicpd +# KEYWORD: shutdown + +# Add the following line to /etc/rc.conf to enable ympd: +# +# ympd_enable="YES" + +. /etc/rc.subr + +name="ympd" +rcvar="${name}_enable" +command="/usr/local/bin/ympd" +pidfile="/var/run/${name}.pid" +start_cmd="ympd_start" + +load_rc_config "$name" +: ${ympd_enable:="NO"} +: ${ympd_user:="nobody"} + +ympd_start() +{ + check_startmsgs && echo "Starting ${name}." + /usr/sbin/daemon -f -p "${pidfile}" -t "${name}" -u "${ympd_user}" "${command}" +} + +run_rc_command "$1" diff --git a/contrib/ympd.service b/contrib/ympd.service index 2289550..7b42ebb 100644 --- a/contrib/ympd.service +++ b/contrib/ympd.service @@ -5,10 +5,12 @@ Requires=network.target local-fs.target [Service] Environment=MPD_HOST=localhost Environment=MPD_PORT=6600 +Environment=MPD_PASSWORD= Environment=WEB_PORT=8080 Environment=YMPD_USER=nobody +Environment=DIRBLE_API_TOKEN=2e223c9909593b94fc6577361a EnvironmentFile=/etc/default/ympd -ExecStart=/usr/bin/ympd --user $YMPD_USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT +ExecStart=/usr/bin/ympd --user $YMPD_USER --mpdpass "$MPD_PASSWORD" --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT --dirbletoken $DIRBLE_API_TOKE Type=simple [Install] diff --git a/htdocs/index.html b/htdocs/index.html index ed77279..9d8f9a3 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -55,6 +55,11 @@ +
+ +
@@ -99,7 +104,7 @@ -
+
@@ -169,6 +174,21 @@
+
+ + + +
+
-
- -
+
+ +
@@ -329,6 +349,7 @@ + diff --git a/htdocs/js/jquery-ui-sortable.min.js b/htdocs/js/jquery-ui-sortable.min.js new file mode 100644 index 0000000..cc4c1af --- /dev/null +++ b/htdocs/js/jquery-ui-sortable.min.js @@ -0,0 +1,5 @@ +/*! http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.sortable.js +* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ + +(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,a=s.name,t.href&&a&&"map"===s.nodeName.toLowerCase()?(o=e("img[usemap=#"+a+"]")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,a=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.3",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(a.css("zIndex"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),s===e)return o[i]===e?null:o[i];o[i]=s}else{if(s===e)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget("ui.mouse",{version:"1.10.3",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).bind("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a="string"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+".preventClickEvent")&&t.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}function i(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))}t.widget("ui.sortable",t.ui.mouse,{version:"1.10.3",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var t=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?"x"===t.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_setOption:function(e,i){"disabled"===e?(this.options[e]=i,this.widget().toggleClass("ui-sortable-disabled",!!i)):t.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):undefined}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var i="x"===this.options.axis||e(this.positionAbs.top+this.offset.click.top,t.top,t.height),s="y"===this.options.axis||e(this.positionAbs.left+this.offset.click.left,t.left,t.width),n=i&&s,o=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return n?this.floating?a&&"right"===a||"down"===o?2:1:o&&("down"===o?2:1):!1},_intersectsWithSides:function(t){var i=e(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),s=e(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),n=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?"right"===o&&s||"left"===o&&!s:n&&("down"===n&&i||"up"===n&&!i)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){var i,s,n,o,a=[],r=[],h=this._connectWith();if(h&&e)for(i=h.length-1;i>=0;i--)for(n=t(h[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&r.push([t.isFunction(o.options.items)?o.options.items.call(o.element):t(o.options.items,o.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),o]);for(r.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),i=r.length-1;i>=0;i--)r[i][0].each(function(){a.push(this)});return t(a)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]).addClass(i||e.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===s?e.currentItem.children().each(function(){t(" ",e.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(n)}):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_contactContainers:function(s){var n,o,a,r,h,l,c,u,d,p,f=null,g=null;for(n=this.containers.length-1;n>=0;n--)if(!t.contains(this.currentItem[0],this.containers[n].element[0]))if(this._intersectsWith(this.containers[n].containerCache)){if(f&&t.contains(this.containers[n].element[0],f.element[0]))continue;f=this.containers[n],g=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger("out",s,this._uiHash(this)),this.containers[n].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[g].containerCache.over||(this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1);else{for(a=1e4,r=null,p=f.floating||i(this.currentItem),h=p?"left":"top",l=p?"width":"height",c=this.positionAbs[h]+this.offset.click[h],o=this.items.length-1;o>=0;o--)t.contains(this.containers[g].element[0],this.items[o].item[0])&&this.items[o].item[0]!==this.currentItem[0]&&(!p||e(this.positionAbs.top+this.offset.click.top,this.items[o].top,this.items[o].height))&&(u=this.items[o].item.offset()[h],d=!1,Math.abs(u-c)>Math.abs(u+this.items[o][l]-c)&&(d=!0,u+=this.items[o][l]),a>Math.abs(u-c)&&(a=Math.abs(u-c),r=this.items[o],this.direction=d?"up":"down"));if(!r&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[g])return;r?this._rearrange(s,r,null,!0):this._rearrange(s,null,this.containers[g].element,!0),this._trigger("change",s,this._uiHash()),this.containers[g]._trigger("change",s,this._uiHash(this)),this.currentContainer=this.containers[g],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,t("document"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(t("document"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.leftthis.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){this.reverting=!1;var i,s=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(i in this._storedCSS)("auto"===this._storedCSS[i]||"static"===this._storedCSS[i])&&(this._storedCSS[i]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&s.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||s.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(s.push(function(t){this._trigger("remove",t,this._uiHash())}),s.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),s.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),i=this.containers.length-1;i>=0;i--)e||s.push(function(t){return function(e){t._trigger("deactivate",e,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(s.push(function(t){return function(e){t._trigger("out",e,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!e){for(this._trigger("beforeStop",t,this._uiHash()),i=0;s.length>i;i++)s[i].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}if(e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!e){for(i=0;s.length>i;i++)s[i].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})})(jQuery); \ No newline at end of file diff --git a/htdocs/js/mpd.js b/htdocs/js/mpd.js index 6000b79..5c8a8d1 100644 --- a/htdocs/js/mpd.js +++ b/htdocs/js/mpd.js @@ -1,6 +1,6 @@ /* ympd (c) 2013-2014 Andrew Karpow - This project's homepage is: http://www.ympd.org + This project's homepage is: https://www.ympd.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,6 +29,9 @@ var dirble_selected_cat = ""; var dirble_catid = ""; var dirble_page = 1; var isTouch = Modernizr.touch ? 1 : 0; +var filter = undefined; +var dirble_api_token = ""; +var dirble_stations = false; var app = $.sammy(function() { @@ -36,6 +39,7 @@ var app = $.sammy(function() { current_app = 'queue'; $('#breadcrump').addClass('hide'); + $('#filter').addClass('hide'); $('#salamisandwich').removeClass('hide').find("tr:gt(0)").remove(); $('#dirble_panel').addClass('hide'); socket.send('MPD_API_GET_QUEUE,'+pagination); @@ -63,7 +67,8 @@ var app = $.sammy(function() { browsepath = this.params['splat'][1]; pagination = parseInt(this.params['splat'][0]); current_app = 'browse'; - $('#breadcrump').removeClass('hide').empty().append("
  • root
  • "); + $('#breadcrump').removeClass('hide').empty().append("
  • root
  • "); + $('#filter').removeClass('hide'); $('#salamisandwich').removeClass('hide').find("tr:gt(0)").remove(); $('#dirble_panel').addClass('hide'); socket.send('MPD_API_GET_BROWSE,'+pagination+','+(browsepath ? browsepath : "/")); @@ -127,7 +132,13 @@ var app = $.sammy(function() { dirble_catid = this.params['splat'][0]; dirble_page = this.params['splat'][1]; - dirble_load_stations(); + dirble_stations = true; + + if(dirble_api_token) { + dirble_load_stations(); + } else { + getDirbleApiToken(); + } }); @@ -144,7 +155,13 @@ var app = $.sammy(function() { $('#panel-heading').text("Dirble"); $('#dirble').addClass('active'); - dirble_load_categories(); + dirble_stations = false; + + if(dirble_api_token) { + dirble_load_categories(); + } else { + getDirbleApiToken(); + } }); this.get("/", function(context) { @@ -178,6 +195,8 @@ $(document).ready(function(){ else if ($.cookie("notification") === "true") $('#btnnotify').addClass("active") + + add_filter(); }); @@ -224,27 +243,39 @@ function webSocketConnect() { ""); } - if(obj.data[obj.data.length-1].pos + 1 >= pagination + MAX_ELEMENTS_PER_PAGE) + if(obj.data.length && obj.data[obj.data.length-1].pos + 1 >= pagination + MAX_ELEMENTS_PER_PAGE) $('#next').removeClass('hide'); if(pagination > 0) $('#prev').removeClass('hide'); if ( isTouch ) { $('#salamisandwich > tbody > tr > td:last-child').append( "" + + "onclick=\"trash($(this).parents('tr'));\">" + ""); } else { $('#salamisandwich > tbody > tr').on({ mouseover: function(){ + var doomed = $(this); + if ( $('#btntrashmodeup').hasClass('active') ) + doomed = $("#salamisandwich > tbody > tr:lt(" + ($(this).index() + 1) + ")"); + if ( $('#btntrashmodedown').hasClass('active') ) + doomed = $("#salamisandwich > tbody > tr:gt(" + ($(this).index() - 1) + ")"); + $.each(doomed, function(){ if($(this).children().last().has("a").length == 0) $(this).children().last().append( "" + + "onclick=\"trash($(this).parents('tr'));\">" + "") .find('a').fadeTo('fast',1); + }); }, mouseleave: function(){ - $(this).children().last().find("a").stop().remove(); + var doomed = $(this); + if ( $('#btntrashmodeup').hasClass('active') ) + doomed = $("#salamisandwich > tbody > tr:lt(" + ($(this).index() + 1) + ")"); + if ( $('#btntrashmodedown').hasClass('active') ) + doomed = $("#salamisandwich > tbody > tr:gt(" + ($(this).index() - 1) + ")"); + $.each(doomed, function(){$(this).children().last().find("a").stop().remove();}); } }); }; @@ -256,6 +287,22 @@ function webSocketConnect() { $(this).addClass('active'); }, }); + //Helper function to keep table row from collapsing when being sorted + var fixHelperModified = function(e, tr) { + var $originals = tr.children(); + var $helper = tr.clone(); + $helper.children().each(function(index) + { + $(this).width($originals.eq(index).width()) + }); + return $helper; + }; + + //Make queue table sortable + $("#salamisandwich > tbody").sortable({ + helper: fixHelperModified, + stop: function(event,ui) {renumber_table('#salamisandwich',ui.item)} + }).disableSelection(); break; case "search": $('#wait').modal('hide'); @@ -267,19 +314,37 @@ function webSocketConnect() { * some browsers, such as Safari, from changing the normalization form of the * URI from NFD to NFC, breaking our link with MPD. */ + if ($('#salamisandwich > tbody').is(':ui-sortable')) { + $('#salamisandwich > tbody').sortable('destroy'); + } for (var item in obj.data) { switch(obj.data[item].type) { case "directory": + var clazz = 'dir'; + if (filter !== undefined) { + var first = obj.data[item].dir[0]; + if (filter === "#" && isNaN(first)) { + clazz += ' hide'; + } else if (filter >= "A" && filter <= "Z" && first.toUpperCase() !== filter) { + clazz += ' hide'; + } else if (filter === "||") { + clazz += ' hide'; + } + } $('#salamisandwich > tbody').append( - "" + + "" + "" + "" + basename(obj.data[item].dir) + "" + "" ); break; case "playlist": + var clazz = 'plist'; + if (filter !== "||") { + clazz += ' hide'; + } $('#salamisandwich > tbody').append( - "" + + "" + "" + "" + basename(obj.data[item].plist) + "" + "" @@ -466,6 +531,8 @@ function webSocketConnect() { $('#album').text(""); $('#artist').text(""); + $('#btnlove').removeClass("active"); + $('#currenttrack').text(" " + obj.data.title); var notification = "

    " + obj.data.title + "

    "; @@ -493,6 +560,15 @@ function webSocketConnect() { if(obj.data.passwort_set) $('#mpd_password_set').removeClass('hide'); break; + case "dirbleapitoken": + dirble_api_token = obj.data; + + if(dirble_stations) { + dirble_load_stations(); + } else { + dirble_load_categories(); + } + break; case "error": $('.top-right').notify({ message:{text: obj.data}, @@ -525,6 +601,7 @@ function get_appropriate_ws_url() { var pcol; var u = document.URL; + var separator; /* /* We open the websocket encrypted if this page came on an @@ -542,7 +619,13 @@ function get_appropriate_ws_url() u = u.split('#'); - return pcol + u[0] + "/ws"; + if (/\/$/.test(u[0])) { + separator = ""; + } else { + separator = "/"; + } + + return pcol + u[0] + separator + "ws"; } var updateVolumeIcon = function(volume) @@ -594,10 +677,41 @@ function clickPlay() { socket.send('MPD_API_SET_PAUSE'); } +function trash(tr) { + if ( $('#btntrashmodeup').hasClass('active') ) { + socket.send('MPD_API_RM_RANGE,0,' + (tr.index() + 1)); + tr.remove(); + } else if ( $('#btntrashmodesingle').hasClass('active') ) { + socket.send('MPD_API_RM_TRACK,' + tr.attr('trackid')); + tr.remove(); + } else if ( $('#btntrashmodedown').hasClass('active') ) { + socket.send('MPD_API_RM_RANGE,' + tr.index() + ',-1'); + tr.remove(); + }; +} + +function renumber_table(tableID,item) { + was = item.children("td").first().text();//Check if first item exists! + is = item.index() + 1;//maybe add pagination + + if (was != is) { + socket.send("MPD_API_MOVE_TRACK," + was + "," + is); + socket.send('MPD_API_GET_QUEUE,'+pagination); + } +} + function basename(path) { return path.split('/').reverse()[0]; } +function clickLove() { + socket.send("MPD_API_SEND_MESSAGE,mpdas," + ($('#btnlove').hasClass('active') ? "unlove" : "love")); + if ( $('#btnlove').hasClass('active') ) + $('#btnlove').removeClass("active"); + else + $('#btnlove').addClass("active"); +} + $('#btnrandom').on('click', function (e) { socket.send("MPD_API_TOGGLE_RANDOM," + ($(this).hasClass('active') ? 0 : 1)); @@ -620,6 +734,11 @@ function toggleoutput(button, id) { socket.send("MPD_API_TOGGLE_OUTPUT,"+id+"," + ($(button).hasClass('active') ? 0 : 1)); } +$('#trashmode').children("button").on('click', function(e) { + $('#trashmode').children("button").removeClass("active"); + $(this).addClass("active"); +}); + $('#btnnotify').on('click', function (e) { if($.cookie("notification") === "true") { $.cookie("notification", false); @@ -652,6 +771,10 @@ function getHost() { $('#mpd_pw_con').keypress(onEnter); } +function getDirbleApiToken() { + socket.send('MPD_API_GET_DIRBLEAPITOKEN'); +} + $('#search').submit(function () { app.setLocation("#/search/"+$('#search > div > input').val()); $('#wait').modal('show'); @@ -694,7 +817,7 @@ $('.page-btn').on('click', function (e) { function addStream() { if($('#streamurl').val().length > 0) { - socket.send('MPD_API_ADD_TRACK,'+$('#streamurl').val()); + socket.send('MPD_API_ADD_TRACK,'+$('#streamurl').val()); } $('#streamurl').val(""); $('#addstream').modal('hide'); @@ -702,7 +825,7 @@ function addStream() { function saveQueue() { if($('#playlistname').val().length > 0) { - socket.send('MPD_API_SAVE_QUEUE,'+$('#playlistname').val()); + socket.send('MPD_API_SAVE_QUEUE,'+$('#playlistname').val()); } $('#savequeue').modal('hide'); } @@ -754,7 +877,7 @@ function songNotify(title, artist, album) { if(typeof album != 'undefined' && album.length > 0) textNotification += "\n " + album; - var notification = new Notification(title, {icon: 'assets/favicon.ico', body: textNotification}); + var notification = new Notification(title, {icon: 'assets/favicon.ico', body: textNotification}); setTimeout(function(notification) { notification.close(); }, 3000, notification); @@ -784,7 +907,7 @@ function dirble_load_categories() { dirble_page = 1; - $.getJSON( "http://api.dirble.com/v2/categories?token=2e223c9909593b94fc6577361a", function( data ) { + $.getJSON( "https://api.dirble.com/v2/categories?token=" + dirble_api_token, function( data ) { $('#dirble_loading').addClass('hide'); @@ -832,7 +955,7 @@ function dirble_load_categories() { function dirble_load_stations() { - $.getJSON( "http://api.dirble.com/v2/category/"+dirble_catid+"/stations?page="+dirble_page+"&per_page=20&token=2e223c9909593b94fc6577361a", function( data ) { + $.getJSON( "https://api.dirble.com/v2/category/"+dirble_catid+"/stations?page="+dirble_page+"&per_page=20&token=" + dirble_api_token, function( data ) { $('#dirble_loading').addClass('hide'); if (data.length == 20) $('#next').removeClass('hide'); @@ -859,7 +982,7 @@ function dirble_load_stations() { click: function() { var _this = $(this); - $.getJSON( "http://api.dirble.com/v2/station/"+$(this).attr("radioid")+"?token=2e223c9909593b94fc6577361a", function( data ) { + $.getJSON( "https://api.dirble.com/v2/station/"+$(this).attr("radioid")+"?token=" + dirble_api_token, function( data ) { socket.send("MPD_API_ADD_TRACK," + data.streams[0].stream); $('.top-right').notify({ @@ -877,7 +1000,7 @@ function dirble_load_stations() { "").find('a').click(function(e) { e.stopPropagation(); - $.getJSON( "http://api.dirble.com/v2/station/"+_this.attr("radioid")+"?token=2e223c9909593b94fc6577361a", function( data ) { + $.getJSON( "https://api.dirble.com/v2/station/"+_this.attr("radioid")+"?token=" + dirble_api_token, function( data ) { socket.send("MPD_API_ADD_PLAY_TRACK," + data.streams[0].stream); $('.top-right').notify({ @@ -898,7 +1021,7 @@ function dirble_load_stations() { click: function() { var _this = $(this); - $.getJSON( "http://api.dirble.com/v2/station/"+$(this).attr("radioid")+"?token=2e223c9909593b94fc6577361a", function( data ) { + $.getJSON( "https://api.dirble.com/v2/station/"+$(this).attr("radioid")+"?token=" + dirble_api_token, function( data ) { socket.send("MPD_API_ADD_TRACK," + data.streams[0].stream); $('.top-right').notify({ @@ -916,7 +1039,7 @@ function dirble_load_stations() { "").find('a').click(function(e) { e.stopPropagation(); - $.getJSON( "http://api.dirble.com/v2/station/"+_this.attr("radioid")+"?token=2e223c9909593b94fc6577361a", function( data ) { + $.getJSON( "https://api.dirble.com/v2/station/"+_this.attr("radioid")+"?token=" + dirble_api_token, function( data ) { socket.send("MPD_API_ADD_PLAY_TRACK," + data.streams[0].stream); $('.top-right').notify({ @@ -934,3 +1057,56 @@ function dirble_load_stations() { }); }); } + +function set_filter (c) { + filter = c; + + $.each($('#salamisandwich > tbody > tr.dir'), function(i, line) { + var first = $(line).attr('uri')[0]; + + if (filter === undefined) { + $(line).removeClass('hide'); + } + + else if (filter === "#") { + if (!isNaN(first)) { + $(line).removeClass('hide'); + } else { + $(line).addClass('hide'); + } + } + + else if (filter >= "A" && filter <= "Z") { + if (first.toUpperCase() === filter) { + $(line).removeClass('hide'); + } else { + $(line).addClass('hide'); + } + } + + else if (filter === "||") { + $(line).addClass('hide'); + } + }); + + $.each($('#salamisandwich > tbody > tr.plist'), function(i, line) { + if (filter === undefined) { + $(line).removeClass('hide'); + } else if (filter === "||") { + $(line).removeClass('hide'); + } else { + $(line).addClass('hide'); + } + }); +} + +function add_filter () { + $('#filter').append(' #'); + + for (i = 65; i <= 90; i++) { + var c = String.fromCharCode(i); + $('#filter').append(' ' + c + ''); + } + + $('#filter').append(' '); +} diff --git a/src/mpd_client.c b/src/mpd_client.c index 7271984..2911e46 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "mpd_client.h" #include "config.h" @@ -63,7 +64,8 @@ int callback_mpd(struct mg_connection *c) return MG_TRUE; if(mpd.conn_state != MPD_CONNECTED && cmd_id != MPD_API_SET_MPDHOST && - cmd_id != MPD_API_GET_MPDHOST && cmd_id != MPD_API_SET_MPDPASS) + cmd_id != MPD_API_GET_MPDHOST && cmd_id != MPD_API_SET_MPDPASS && + cmd_id != MPD_API_GET_DIRBLEAPITOKEN) return MG_TRUE; switch(cmd_id) @@ -93,6 +95,18 @@ int callback_mpd(struct mg_connection *c) if(sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf)) mpd_run_delete_id(mpd.conn, uint_buf); break; + case MPD_API_RM_RANGE: + if(sscanf(c->content, "MPD_API_RM_RANGE,%u,%u", &uint_buf, &uint_buf_2)) + mpd_run_delete_range(mpd.conn, uint_buf, uint_buf_2); + break; + case MPD_API_MOVE_TRACK: + if (sscanf(c->content, "MPD_API_MOVE_TRACK,%u,%u", &uint_buf, &uint_buf_2) == 2) + { + uint_buf -= 1; + uint_buf_2 -= 1; + mpd_run_move(mpd.conn, uint_buf, uint_buf_2); + } + break; case MPD_API_PLAY_TRACK: if(sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf)) mpd_run_play_id(mpd.conn, uint_buf); @@ -229,6 +243,27 @@ out_save_queue: out_search: free(p_charbuf); break; + case MPD_API_SEND_MESSAGE: + p_charbuf = strdup(c->content); + if(strcmp(strtok(p_charbuf, ","), "MPD_API_SEND_MESSAGE")) + goto out_send_message; + + if((token = strtok(NULL, ",")) == NULL) + goto out_send_message; + + free(p_charbuf); + p_charbuf = strdup(get_arg1(c->content)); + + if ( strtok(p_charbuf, ",") == NULL ) + goto out_send_message; + + if ( (token = strtok(NULL, ",")) == NULL ) + goto out_send_message; + + mpd_run_send_message(mpd.conn, p_charbuf, token); +out_send_message: + free(p_charbuf); + break; #ifdef WITH_MPD_HOST_CHANGE /* Commands allowed when disconnected from MPD server */ case MPD_API_SET_MPDHOST: @@ -256,6 +291,10 @@ out_host_change: "{\"host\" : \"%s\", \"port\": \"%d\", \"passwort_set\": %s}" "}", mpd.host, mpd.port, mpd.password ? "true" : "false"); break; + case MPD_API_GET_DIRBLEAPITOKEN: + n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"dirbleapitoken\", \"" + "data\": \"%s\"}", dirble_api_token); + break; case MPD_API_SET_MPDPASS: p_charbuf = strdup(c->content); if(strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDPASS")) diff --git a/src/mpd_client.h b/src/mpd_client.h index dd78af9..447dd56 100644 --- a/src/mpd_client.h +++ b/src/mpd_client.h @@ -40,14 +40,18 @@ X(MPD_API_GET_QUEUE) \ X(MPD_API_GET_BROWSE) \ X(MPD_API_GET_MPDHOST) \ + X(MPD_API_GET_DIRBLEAPITOKEN) \ X(MPD_API_ADD_TRACK) \ X(MPD_API_ADD_PLAY_TRACK) \ X(MPD_API_ADD_PLAYLIST) \ X(MPD_API_PLAY_TRACK) \ X(MPD_API_SAVE_QUEUE) \ X(MPD_API_RM_TRACK) \ + X(MPD_API_RM_RANGE) \ X(MPD_API_RM_ALL) \ + X(MPD_API_MOVE_TRACK) \ X(MPD_API_SEARCH) \ + X(MPD_API_SEND_MESSAGE) \ X(MPD_API_SET_VOLUME) \ X(MPD_API_SET_PAUSE) \ X(MPD_API_SET_PLAY) \ @@ -94,6 +98,8 @@ struct t_mpd { unsigned queue_version; } mpd; +char dirble_api_token[28]; + struct t_mpd_client_session { int song_id; unsigned queue_version; diff --git a/src/ympd.c b/src/ympd.c index afa25c6..00b82ee 100644 --- a/src/ympd.c +++ b/src/ympd.c @@ -80,17 +80,21 @@ int main(int argc, char **argv) mpd.port = 6600; strcpy(mpd.host, "127.0.0.1"); + strcpy(dirble_api_token, "2e223c9909593b94fc6577361a"); + static struct option long_options[] = { {"host", required_argument, 0, 'h'}, {"port", required_argument, 0, 'p'}, {"webport", required_argument, 0, 'w'}, + {"dirbletoken", required_argument, 0, 'd'}, {"user", required_argument, 0, 'u'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 0 }, + {"mpdpass", required_argument, 0, 'm'}, {0, 0, 0, 0 } }; - while((n = getopt_long(argc, argv, "h:p:w:u:v", + while((n = getopt_long(argc, argv, "h:p:w:u:vm:", long_options, &option_index)) != -1) { switch (n) { case 'h': @@ -102,9 +106,15 @@ int main(int argc, char **argv) case 'w': webport = strdup(optarg); break; + case 'd': + strncpy(dirble_api_token, optarg, sizeof(dirble_api_token)); + break; case 'u': run_as_user = strdup(optarg); break; + case 'm': + mpd.password = strdup(optarg); + break; case 'v': fprintf(stdout, "ympd %d.%d.%d\n" "Copyright (C) 2014 Andrew Karpow \n" @@ -118,7 +128,9 @@ int main(int argc, char **argv) " -p, --port \t\tconnect to mpd at port [6600]\n" " -w, --webport [ip:]\tlisten interface/port for webserver [8080]\n" " -u, --user \t\tdrop priviliges to user after socket bind\n" + " -d, --dirbletoken \tDirble API token\n" " -V, --version\t\t\tget version\n" + " -m, --mpdpass \tspecifies the password to use when connecting to mpd\n" " --help\t\t\t\tthis help\n" , argv[0]); return EXIT_FAILURE; diff --git a/ympd.1 b/ympd.1 index d94ed30..c8160a6 100644 --- a/ympd.1 +++ b/ympd.1 @@ -23,6 +23,9 @@ specifies the port for the webserver to listen to, defaults to 8080 \fB\-u\fR, \fB\-\-user username\fR drop privileges to the provided username after socket binding .TP +\fB\-m\fR, \fB\-\-mpdpass password\fR +specifies the password to use when connecting to mpd +.TP \fB\-V\fR, \fB\-\-version\fR print version and exit .TP