From 45a41f663418acb95dfb588a334411d62a545947 Mon Sep 17 00:00:00 2001 From: Ovis Date: Sun, 10 Jan 2016 21:20:50 +0100 Subject: [PATCH 01/27] Separation of macro engine --- .gitignore | 2 + app.js | 157 +++++++------- lib/gpio.js | 29 +++ lib/lirc.js | 133 ++++++++++++ lib/macro-constants.js | 6 + lib/macro-manager.js | 157 ++++++++++++++ lib/macro.js | 147 +++++++++++++ lib/macros.js | 33 --- package.json | 13 +- templates/index.swig | 3 +- test/fixtures/config.json | 62 +++--- test/lib/lirc.js | 104 ++++++++++ test/lib/macro-manager.js | 422 ++++++++++++++++++++++++++++++++++++++ test/lib/macros.js | 76 ------- test/lirc_web.js | 31 ++- 15 files changed, 1137 insertions(+), 238 deletions(-) create mode 100644 lib/lirc.js create mode 100644 lib/macro-constants.js create mode 100644 lib/macro-manager.js create mode 100644 lib/macro.js delete mode 100644 lib/macros.js create mode 100644 test/lib/lirc.js create mode 100644 test/lib/macro-manager.js delete mode 100644 test/lib/macros.js diff --git a/.gitignore b/.gitignore index 1c88d31..7fd8018 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ node_modules/ npm-debug.log server.cert server.key +.idea +lirc_web.iml diff --git a/app.js b/app.js index df7bbc7..9f335ea 100644 --- a/app.js +++ b/app.js @@ -4,14 +4,14 @@ var express = require('express'); var logger = require('morgan'); var compress = require('compression'); -var lircNode = require('lirc_node'); +var lirc = require('./lib/lirc'); var consolidate = require('consolidate'); var swig = require('swig'); var labels = require('./lib/labels'); var https = require('https'); var fs = require('fs'); +var macros = require('./lib/macro-manager.js'); var gpio = require('./lib/gpio'); -var macros = require('./lib/macros'); // Precompile templates var JST = { @@ -43,7 +43,7 @@ app.set('view engine', 'jade'); app.use(compress()); app.use(express.static(__dirname + '/static')); -function _init() { +function readConfiguration() { var searchPaths = []; function configure(configFileName) { @@ -52,8 +52,6 @@ function _init() { console.log('Open Source Universal Remote is configured by ' + configFileName); } - lircNode.init(); - // Config file is optional try { try { @@ -73,80 +71,77 @@ function _init() { && config.server.ssl_key && config.server.ssl_port; } -function refineRemotes(myRemotes) { - var newRemotes = {}; - var newRemoteCommands = null; - var remote = null; - - function isBlacklistExisting(remoteName) { - return config.blacklists && config.blacklists[remoteName]; +function overrideConfigurationForDebugOrDevelopment() { + var lircTest; + if (process.env.npm_package_config_test_env) { + console.log('we are in test mode!'); + lircTest = require('./test/lib/lirc'); + lircTest.replaceLircByMock(); + gpio.overrideWiringPi(require('./test/lib/wiring-pi-mock')); + config = require('./test/fixtures/config.json'); + hasServerPortConfig = false; + hasSSLConfig = false; } +} - function getCommandsForRemote(remoteName) { - var remoteCommands = myRemotes[remoteName]; - var blacklist = null; - - if (isBlacklistExisting(remoteName)) { - blacklist = config.blacklists[remoteName]; - - remoteCommands = remoteCommands.filter(function (command) { - return blacklist.indexOf(command) < 0; - }); +function initializeModules(done) { + lirc.init(config, function () { + if (config.gpios) { + gpio.init(config.gpios); } - return remoteCommands; - } - - for (remote in myRemotes) { - newRemoteCommands = getCommandsForRemote(remote); - newRemotes[remote] = newRemoteCommands; - } + if (config.macros) { + if (config.gpios) { + macros.registerDevice(gpio); + } + macros.registerDevice(lirc); + macros.init(config.macros); + } - return newRemotes; + // initialize Labels for remotes / commands + labelFor = labels(config.remoteLabels, config.commandLabels); + done(); + }); } -// Based on node environment, initialize connection to lircNode or use test data -if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') { - lircNode.remotes = require(__dirname + '/test/fixtures/remotes.json'); - config = require(__dirname + '/test/fixtures/config.json'); - gpio.overrideWiringPi(require('./test/lib/wiring-pi-mock')); -} else { - _init(); +function init(done) { + readConfiguration(); + overrideConfigurationForDebugOrDevelopment(); + initializeModules(done); } -// initialize Labels for remotes / commands -labelFor = labels(config.remoteLabels, config.commandLabels); - // Routes // Index app.get('/', function (req, res) { - var refinedRemotes = refineRemotes(lircNode.remotes); - res.send(JST.index({ - remotes: refinedRemotes, - macros: config.macros, + var indexPage = JST.index({ + remotes: lirc.getRemotes(), + macros: macros.getGuiMacroLabels(), repeaters: config.repeaters, gpios: config.gpios, labelForRemote: labelFor.remote, labelForCommand: labelFor.command, - })); + }); + res.send(indexPage); }); // Refresh app.get('/refresh', function (req, res) { - _init(); - res.redirect('/'); + init(function () { + res.redirect('/'); + }); }); // List all remotes in JSON format app.get('/remotes.json', function (req, res) { - res.json(refineRemotes(lircNode.remotes)); + res.json(lirc.getRemotes()); }); // List all commands for :remote in JSON format app.get('/remotes/:remote.json', function (req, res) { - if (lircNode.remotes[req.params.remote]) { - res.json(refineRemotes(lircNode.remotes)[req.params.remote]); + var commands = lirc.getCommandsForRemote(req.params.remote); + if (commands) { + res.json(commands); } else { res.sendStatus(404); } @@ -171,32 +166,23 @@ app.get('/macros.json', function (req, res) { res.json(config.macros); }); -// List all commands for :macro in JSON format -app.get('/macros/:macro.json', function (req, res) { - if (config.macros && config.macros[req.params.macro]) { - res.json(config.macros[req.params.macro]); - } else { - res.sendStatus(404); - } -}); - // Send :remote/:command one time app.post('/remotes/:remote/:command', function (req, res) { - lircNode.irsend.send_once(req.params.remote, req.params.command, function () {}); + lirc.sendOnce(req.params.remote, req.params.command, function () {}); res.setHeader('Cache-Control', 'no-cache'); res.sendStatus(200); }); // Start sending :remote/:command repeatedly app.post('/remotes/:remote/:command/send_start', function (req, res) { - lircNode.irsend.send_start(req.params.remote, req.params.command, function () {}); + lirc.sendStart(req.params.remote, req.params.command, function () {}); res.setHeader('Cache-Control', 'no-cache'); res.sendStatus(200); }); // Stop sending :remote/:command repeatedly app.post('/remotes/:remote/:command/send_stop', function (req, res) { - lircNode.irsend.send_stop(req.params.remote, req.params.command, function () {}); + lirc.sendStop(req.params.remote, req.params.command, function () {}); res.setHeader('Cache-Control', 'no-cache'); res.sendStatus(200); }); @@ -212,8 +198,9 @@ app.post('/gpios/:gpio_pin', function (req, res) { // Execute a macro (a collection of commands to one or more remotes) app.post('/macros/:macro', function (req, res) { - if (config.macros && config.macros[req.params.macro]) { - macros.exec(config.macros[req.params.macro], lircNode); + var macroName = req.params.macro; + if (macros.isMacroDefined(macroName)) { + macros.execute(macroName); res.setHeader('Cache-Control', 'no-cache'); if (config.gpios) { respondWithGpioState(res); @@ -226,27 +213,27 @@ app.post('/macros/:macro', function (req, res) { } }); -gpio.init(config.gpios); - -// Listen (http) -if (hasServerPortConfig) { - port = config.server.port; -} -// only start server, when called as application -if (!module.parent) { - app.listen(port); - console.log('Open Source Universal Remote UI + API has started on port ' + port + ' (http).'); -} +init(function () { + // only start server, when called as application + if (!module.parent) { + // Listen (http) + if (hasServerPortConfig) { + port = config.server.port; + } -// Listen (https) -if (hasSSLConfig) { - sslOptions = { - key: fs.readFileSync(config.server.ssl_key), - cert: fs.readFileSync(config.server.ssl_cert), - }; + app.listen(port); + console.log('Open Source Universal Remote UI + API has started on port ' + port + ' (http).'); - https.createServer(sslOptions, app).listen(config.server.ssl_port); + // Listen (https) + if (hasSSLConfig) { + sslOptions = { + key: fs.readFileSync(config.server.ssl_key), + cert: fs.readFileSync(config.server.ssl_cert), + }; + https.createServer(sslOptions, app).listen(config.server.ssl_port); - console.log('Open Source Universal Remote UI + API has started on port ' - + config.server.ssl_port + ' (https).'); -} + console.log('Open Source Universal Remote UI + API has started on port ' + + config.server.ssl_port + ' (https).'); + } + } +}); diff --git a/lib/gpio.js b/lib/gpio.js index 1fe3f93..cf0a955 100644 --- a/lib/gpio.js +++ b/lib/gpio.js @@ -1,4 +1,6 @@ +var DEVICE_NAME = 'gpio'; var wpi = require('wiring-pi'); +// var wpi = require('../test/lib/wiring-pi-mock.js'); var gpios = null; function updatePinStates() { @@ -56,11 +58,38 @@ function overrideWiringPi(wpiReplacement) { return origWpi; } +function call(done, command) { + setPin(command[1], command[2]); + done(); +} + +function validateArguments(args) { + var deviceName = args[0]; + var pinName = args[1]; + var state = args[2]; + var returnString = null; + + if (deviceName !== DEVICE_NAME) { + returnString = '\'gpio\' is required as first argument'; + } else if (! findElement(gpios, 'name', pinName)) { + returnString = 'No GPIO Pin configured with name ' + pinName; + } else if (state !== 0 && state !== 1) { + returnString = 'Illegal state ' + state; + } else if (args.length > 3) { + returnString = 'Extra argument'; + } + return returnString === null ? null : returnString + ' in macro step ' + JSON.stringify(args); +} + + module.exports = { + name: DEVICE_NAME, init: init, setPin: setPin, overrideWiringPi: overrideWiringPi, updatePinStates: updatePinStates, togglePin: togglePin, + validateArguments: validateArguments, + call: call, }; // vim: expandtab:tabstop=8:softtabstop=2:shiftwidth=2 diff --git a/lib/lirc.js b/lib/lirc.js new file mode 100644 index 0000000..0d6e9b4 --- /dev/null +++ b/lib/lirc.js @@ -0,0 +1,133 @@ +var lircNode = require('lirc_node'); + +var DEVICE_NAME_FOR_MACROS = 'lirc'; +var blackLists = {}; +var remotes = {}; + +function setConfigToZero() { + blackLists = {}; + remotes = {}; +} + +function setToDefinedValue(value) { + if (value && value !== null) return value; + return {}; +} + + +function overrideHardware(hardware) { + var originalValue = lircNode; + lircNode = hardware; + setConfigToZero(); + return originalValue; +} + +function refineRemotes(myRemotes) { + var newRemotes = {}; + var newRemoteCommands = null; + var remote = null; + + function getCommandsForRemote(remoteName) { + var remoteCommands = myRemotes[remoteName]; + var blacklist = blackLists[remoteName]; + + if (blacklist) { + remoteCommands = remoteCommands.filter(function (command) { + return blacklist.indexOf(command) < 0; + }); + } + + return remoteCommands; + } + + for (remote in myRemotes) { + newRemoteCommands = getCommandsForRemote(remote); + newRemotes[remote] = newRemoteCommands; + } + + return newRemotes; +} + +function getRemotes() { + return remotes; +} + +function getCommands(remote) { + return refineRemotes(lircNode.remotes)[remote]; +} + +function sendOnce(remote, command, done) { + lircNode.irsend.send_once(remote, command, done); +} + +function sendStart(remote, command, done) { + lircNode.irsend.send_start(remote, command, done); +} + +function sendStop(remote, command, done) { + lircNode.irsend.send_stop(remote, command, done); +} + +function call(done, originalArguments) { + var args = originalArguments.slice(); + if (args[0] === DEVICE_NAME_FOR_MACROS) { + args.shift(); + } + lircNode.irsend.send_once(args[0], args[1], done); +} + +function validateArguments(originalArguments) { + var args = originalArguments.slice(); + var token = args.shift(); + var commands; + var remoteControlName; + var requestedCommand; + + if (token === DEVICE_NAME_FOR_MACROS) { + token = args.shift(); + } + + remoteControlName = token; + commands = remotes[remoteControlName]; + if (commands === undefined || commands === null) { + return 'No remote known with name ' + remoteControlName; + } + + requestedCommand = args.shift(); + if (requestedCommand === undefined || requestedCommand === null) { + return 'No command known with name ' + requestedCommand; + } + + if (args.length > 0) { + return 'Extra argument.'; + } +} + +function init(config, callback) { + if (! config) { + setConfigToZero(); + if (callback) callback(); + return; + } + + blackLists = setToDefinedValue(config.blacklists); + + lircNode.init(function () { + remotes = refineRemotes(lircNode.remotes); + if (callback) callback(); + }); +} + +module.exports = { + name: DEVICE_NAME_FOR_MACROS, + overrideHardware: overrideHardware, + getRemotes: getRemotes, + getCommandsForRemote: getCommands, + sendOnce: sendOnce, + sendStart: sendStart, + sendStop: sendStop, + validateArguments: validateArguments, + call: call, + init: init, +}; +// vim: expandtab tabstop=8 softtabstop=2 shiftwidth=2 diff --git a/lib/macro-constants.js b/lib/macro-constants.js new file mode 100644 index 0000000..3d7efab --- /dev/null +++ b/lib/macro-constants.js @@ -0,0 +1,6 @@ +module.exports = { + DEFAULT_DELAY: 50, + STATE_SET: 'set', + STATE_NOT_SET: 'clear', + STATE_PENDING_SET: 'going_to_be_set', +}; diff --git a/lib/macro-manager.js b/lib/macro-manager.js new file mode 100644 index 0000000..4d0bf94 --- /dev/null +++ b/lib/macro-manager.js @@ -0,0 +1,157 @@ +var constants = require('./macro-constants'); +var Macro = require('./macro'); +var devices = {}; +var catchAllDevice = {}; +var macroConfigurations = {}; +var macros = {}; +var manager = {}; +var visibleMacroNames = []; +var virtualStates; +var getMacroClosureByName; + +function State(name, value) { + var stateInstance = this; + this.name = name; + this.value = value; + this.providingMacro = null; + this.setProvidingMacro = function (macro) { + this.providingMacro = macro; + }; + this.ensureIsSet = function (done) { + if (this.value === constants.STATE_NOT_SET) { + this.registerWaitingMacro(done); + this.providingMacro(function () {this.notifyWaitingMacros();}); + } else if (stateInstance.value === constants.STATE_PENDING_SET) { + // warten + } else { + done(); + } + }; + + this.notifyWaitingMacros = function () { + + }; +} + +function StateManager() { + this.stateByName = {}; + + this.registerState = function (name) { + var state = this.stateByName[name]; + if (! state) { + state = new State(name, constants.STATE_NOT_SET); + this.stateByName[name] = state; + } + return state; + }; + + this.registerStateProvider = function (stateName, macro) { + var state = this.registerState(stateName); + state.setProvidingMacro(macro); + }; + + this.setStateByName = function (name, stateValue) { + var state = this.stateByName[name]; + state.value = stateValue; + }; + + this.getStateSummary = function () { + var summary = {}; + var stateName; + for (stateName in this.stateByName) { + summary[stateName] = this.stateByName[stateName].value; + } + return summary; + }; +} + +function registerMacro(macroConfig) { + var macroName = macroConfig.name; + var macro = new Macro(macroConfig, manager); + macroConfigurations[macroName] = macroConfig; + macros[macroName] = macro; + if (macro.isVisibleMacro()) { + visibleMacroNames.push(macroName); + } +} + +function init(newConfiguration) { + newConfiguration.forEach(function (macro) { + registerMacro(macro); + }); +} + +function resetConfiguration() { + devices = {}; + catchAllDevice = {}; + macroConfigurations = {}; + macros = {}; + visibleMacroNames = []; + virtualStates = new StateManager(); +} + +function registerDevice(device) { + devices[device.name] = device; + catchAllDevice = device; +} + +getMacroClosureByName = function (done, macroName) { + var macro = macros[macroName]; + return function () { + macro.execute(done); + }; +}; + +function executeMacroByName(macroName) { + var macro = macros[macroName]; + macro.execute(function () {}); +} + +function getMacroLabelsForDisplay() { + return visibleMacroNames; +} + +function isMacroDefined(macroName) { + return !!macros[macroName]; +} + +function getCurrentStates() { + return virtualStates.getStateSummary(); +} + +function registerState(stateName) { + virtualStates.registerState(stateName); +} + +function setStateByName(stateName, newState) { + virtualStates.setStateByName(stateName, newState); +} + +function getCatchAllDevice() { + return catchAllDevice; +} + +function getDeviceByName(deviceName) { + return devices[deviceName]; +} + +manager = { + registerState: registerState, + setStateByName: setStateByName, + getCatchAllDevice: getCatchAllDevice, + getDeviceByName: getDeviceByName, + isMacroDefined: isMacroDefined, + getMacroClosureByName: getMacroClosureByName, +}; + +module.exports = { + init: init, + resetConfiguration: resetConfiguration, + registerDevice: registerDevice, + execute: executeMacroByName, + getGuiMacroLabels: getMacroLabelsForDisplay, + isMacroDefined: isMacroDefined, + getCurrentStates: getCurrentStates, +}; + +// vim: expandtab tabstop=8 softtabstop=2 shiftwidth=2 diff --git a/lib/macro.js b/lib/macro.js new file mode 100644 index 0000000..b64de57 --- /dev/null +++ b/lib/macro.js @@ -0,0 +1,147 @@ +var constants = require('./macro-constants'); + +function Macro(config, macroManager) { + var macroIdentity = this; + this.callersCallback = null; + this.config = config; + this.name = config.name; + this.macroClosureChain = null; + + function createMacroStep(done, stepDescription) { + var deviceName = stepDescription[0]; + var device = macroManager.getDeviceByName(deviceName); + var failMessage = null; + + if (device) { + failMessage = device.validateArguments(stepDescription); + if (failMessage) { + throw new Error('Can not create macro. Device "' + deviceName + '" rejected ' + + JSON.stringify(stepDescription) + '. Reason: ' + failMessage); + } + } else { + device = macroManager.getCatchAllDevice(); + failMessage = device.validateArguments(stepDescription); + if (failMessage) { + throw new Error('Can not create macro. DeviceName "' + deviceName + '" is not known in ' + + JSON.stringify(stepDescription) + '. Reason: fallback device is ' + device.name + + ' and it can not interpret this line: ' + failMessage); + } + } + + return function () { + device.call(done, stepDescription); + }; + } + + function createDelayMacroStep(done, delayValue) { + return function () { + setTimeout(done, delayValue); + }; + } + + function insertMacroStep(done, stepConfiguration) { + var deviceName = stepConfiguration[0]; + + if (deviceName.toLowerCase() === 'call') { + return macroManager.getMacroClosureByName(done, stepConfiguration[1]); + } else if (deviceName.toLowerCase() === 'delay') { + return createDelayMacroStep(done, stepConfiguration[1]); + } + + return createMacroStep(done, stepConfiguration); + } + + function stepNeedsDefaultDelay(sequence, index) { + var stepConfiguration; + var deviceName; + + if (index < 0) { + return false; + } + + stepConfiguration = sequence[index]; + deviceName = stepConfiguration[0]; + return deviceName.toLowerCase() !== 'delay'; + } + + function insertMacroFromSequence(callWhenDone, sequence, defaultDelay) { + var i; + var done = callWhenDone; + + for (i = sequence.length - 1; i >= 0; i--) { + done = insertMacroStep(done, sequence[i].slice()); + if (stepNeedsDefaultDelay(sequence, i - 1)) { + done = createDelayMacroStep(done, defaultDelay); + } + } + + return done; + } + + this.createClosureChain = function () { + var defaultDelay = config.defaultDelay ? + config.defaultDelay : constants.DEFAULT_DELAY; + var sequence = config.sequence; + return insertMacroFromSequence(this.notifyListeners, sequence, defaultDelay); + }; + + this.isProvidingState = function () { + return config.provides + && config.provides !== null + && config.provides !== ''; + }; + + this.isRequiringState = function () { + return config.requires + && config.requires !== null + && config.requires !== ''; + }; + + this.isResettingState = function () { + return config.resets + && config.resets !== null + && config.resets !== ''; + }; + + this.notifyListeners = function () { + var callback; + + if (macroIdentity.isProvidingState()) { + macroManager.setStateByName([config.provides], constants.STATE_SET); + } + if (macroIdentity.isResettingState()) { + macroManager.setStateByName([config.resets], constants.STATE_NOT_SET); + } + if (macroIdentity.callersCallback !== null) { + callback = macroIdentity.callersCallback; + macroIdentity.callersCallback = null; + callback(); + } + }; + + this.isVisibleMacro = function () { + return !(config.hidden && config.hidden === true); + }; + + this.registerStates = function () { + if (this.isProvidingState()) { + macroManager.registerState(config.provides); + } + if (this.isRequiringState()) { + macroManager.registerState(config.requires); + } + if (this.isResettingState()) { + macroManager.registerState(config.resets); + } + }; + + this.execute = function (done) { + this.callersCallback = done; + this.macroClosureChain(); + }; + + this.macroClosureChain = this.createClosureChain(); + this.registerStates(); +} + +module.exports = Macro; diff --git a/lib/macros.js b/lib/macros.js deleted file mode 100644 index f3b3b53..0000000 --- a/lib/macros.js +++ /dev/null @@ -1,33 +0,0 @@ -var gpio = require('./gpio'); - -function exec(macro, lircNode, iter) { - var i = iter || 0; - - // select the command from the sequence - var command = macro[i]; - - if (!command) { return false; } - - i = i + 1; - - // if the command is delay, wait N msec and then execute next command - if (command[0] === 'delay') { - setTimeout(function () { - exec(macro, lircNode, i); - }, command[1]); - } else if (command[0] === 'gpio') { - gpio.setPin(command[1], command[2]); - exec(macro, lircNode, i); - } else { - // By default, wait 100msec before calling next command - lircNode.irsend.send_once(command[0], command[1], function () { - setTimeout(function () { - exec(macro, lircNode, i); - }, 100); - }); - } - - return true; -} - -exports.exec = exec; diff --git a/package.json b/package.json index 26da2b5..f1ff678 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "lirc_node": "0.0.4", "lodash": "^3.10.1", "morgan": "^1.6.1", - "swig": "^1.4.2", - "wiring-pi": "2.1.0" + "wiring-pi": "^2.1.0", + "swig": "^1.4.2" }, "devDependencies": { "eslint": "^1.10.0", @@ -36,10 +36,13 @@ "bin": { "lirc_web": "./app.js" }, + "config": { + "test_env": true + }, "scripts": { - "test": "NODE_ENV=test mocha --require should --require test/common.js --reporter dot test/**", - "test:watch": "NODE_ENV=test mocha --require should --require test/common.js --watch --reporter dot lib/** test/** ", - "lint-js": "eslint app.js lib/** test/**" + "test": "mocha --require should --require test/common.js --reporter min app.js test/*.js test/*/*.js lib/*.js", + "test:watch": "mocha --require should --require test/common.js --watch --reporter min app.js test/*.js test/*/*.js lib/*.js", + "lint-js": "eslint app.js lib test" }, "repository": { "type": "git", diff --git a/templates/index.swig b/templates/index.swig index 10f80f8..2bb08d8 100644 --- a/templates/index.swig +++ b/templates/index.swig @@ -30,8 +30,7 @@
diff --git a/test/fixtures/config.json b/test/fixtures/config.json index 8bac125..21cb847 100644 --- a/test/fixtures/config.json +++ b/test/fixtures/config.json @@ -5,33 +5,41 @@ "VolumeDown": true } }, - "macros": { - "Play Xbox 360": [ - [ "gpio", "TV", 1], - [ "gpio", "Receiver", 1], - [ "gpio", "Xbox", 1], - [ "SonyTV", "Power" ], - [ "SonyTV", "Xbox360" ], - [ "Yamaha", "Power" ], - [ "Yamaha", "Xbox360" ], - [ "Xbox360", "Power" ] - ], - "Listen to Music / Jams": [ - [ "gpio", "Receiver", 1], - [ "Yamaha", "Power" ], - [ "Yamaha", "AirPlay" ] - ], - "all off": [ - [ "gpio", "TV", 0], - [ "gpio", "Receiver", 0], - [ "gpio", "Xbox", 0] - ], - "Macro With Delay": [ - [ "delay", 500 ], - [ "Yamaha", "Power" ] - ] - - }, + "macros": [ + { + "name": "Play Xbox 360", + "sequence": [ + [ "gpio", "TV", 1], + [ "gpio", "Receiver", 1], + [ "gpio", "Xbox", 1], + [ "SonyTV", "Power" ], + [ "SonyTV", "Xbox360" ], + [ "Yamaha", "Power" ], + [ "Yamaha", "Xbox360" ], + [ "Xbox360", "Power" ] + ] + },{ + "name": "Listen to Music / Jams", + "sequence": [ + [ "gpio", "Receiver", 1], + [ "Yamaha", "Power" ], + [ "Yamaha", "AirPlay" ] + ] + },{ + "name": "all off", + "sequence": [ + [ "gpio", "TV", 0], + [ "gpio", "Receiver", 0], + [ "gpio", "Xbox", 0] + ] + },{ + "name": "Macro With Delay", + "sequence": [ + [ "delay", 500 ], + [ "Yamaha", "Power" ] + ] + } + ], "commandLabels": { "LircNamespace": { "KEY_POWER": "Power", diff --git a/test/lib/lirc.js b/test/lib/lirc.js new file mode 100644 index 0000000..7445e57 --- /dev/null +++ b/test/lib/lirc.js @@ -0,0 +1,104 @@ +var lirc = require('../../lib/lirc'); +var assert = require('assert'); +var sinon = require('sinon'); + +var XBOX_COMMANDS = ['Y', 'X', 'A', 'B']; +var SONY_COMMANDS = ['Power', 'VolumeUp', 'VolumeDown', 'TeleTubbies']; +var FILTERED_SONY_COMMANDS = ['Power', 'VolumeUp', 'VolumeDown']; +var YAMAHA_COMMANDS = ['Power', 'VolumeUp', 'VolumeDown']; + +var REMOTES_LIST = { + Yamaha: YAMAHA_COMMANDS, + SonyTV: SONY_COMMANDS, + Xbox360: XBOX_COMMANDS, +}; + +var FILTERED_REMOTES_LIST = { + Yamaha: YAMAHA_COMMANDS, + SonyTV: FILTERED_SONY_COMMANDS, + Xbox360: XBOX_COMMANDS, +}; + +var CONFIG = { + blacklists: { + SonyTV: [ + 'TeleTubbies', + ], + }, +}; + +var deviceMock; + +function getDeviceMock(remotes) { + var newMock = { + remotes: {}, + irsend: {}, + }; + newMock.init = function (callback) { callback(); }; + newMock.irsend.send_once = sinon.spy(); + newMock.irsend.send_start = sinon.spy(); + newMock.irsend.send_stop = sinon.spy(); + newMock.remotes = remotes; + return newMock; +} + +function replaceLircByMock() { + deviceMock = getDeviceMock(); + deviceMock.remotes = REMOTES_LIST; + lirc.overrideHardware(deviceMock); +} + +describe('lirc binding', function () { + before(function () { + replaceLircByMock(); + lirc.init(CONFIG); + }); + + describe('remotes', function () { + it('should give a list of all known remotes and their filtered commands', function () { + assert.deepEqual(lirc.getRemotes(), FILTERED_REMOTES_LIST); + }); + + it('should give a list of commands of a known remote', function () { + assert.deepEqual(lirc.getCommandsForRemote('Xbox360'), XBOX_COMMANDS); + }); + + it('should give a shortened list of commands if a blacklist is set', function () { + lirc.init(CONFIG); + assert.deepEqual(lirc.getCommandsForRemote('SonyTV'), ['Power', 'VolumeUp', 'VolumeDown']); + }); + }); + + describe('macro support', function () { + it('should reject a command, if the number of arguments does not fit', function () { + assert.notEqual(lirc.validateArguments(['SonyTV', 'VolumeUp', 'extra']), null, + 'known remote, command, extra arg'); + assert.notEqual(lirc.validateArguments(['lirc', 'SonyTV', 'VolumeUp', 'extra']), null, + 'device key, known remote, command, extra arg'); + assert.notEqual(lirc.validateArguments(['lirc', 'SonyTV']), null, + 'device key, known remote, missing command'); + }); + + it('should accpet a command, if the number of arguments fits', function () { + assert.equal(lirc.validateArguments(['lirc', 'SonyTV', 'VolumeUp']), null, + 'device key, known remote, command'); + assert.equal(lirc.validateArguments(['SonyTV', 'VolumeUp']), null, + 'known remote, command'); + }); + + it('should call lircNode.irsend.send_once when call is performed', function () { + var callback = sinon.spy(); + deviceMock.irsend.send_once = sinon.spy(); + lirc.call(callback, ['lirc', 'SonyTV', 'VolumeUp']); + assert.deepEqual(deviceMock.irsend.send_once.lastCall.args, ['SonyTV', 'VolumeUp', callback]); + lirc.call(callback, ['SonyTV', 'VolumeUp']); + assert.deepEqual(deviceMock.irsend.send_once.lastCall.args, ['SonyTV', 'VolumeUp', callback]); + }); + }); +}); + +module.exports = { + getDeviceMock: getDeviceMock, + replaceLircByMock: replaceLircByMock, +}; +// vim: expandtab tabstop=8 softtabstop=2 shiftwidth=2 diff --git a/test/lib/macro-manager.js b/test/lib/macro-manager.js new file mode 100644 index 0000000..4dbed7c --- /dev/null +++ b/test/lib/macro-manager.js @@ -0,0 +1,422 @@ +var sinon = require('sinon'); +var assert = require('assert'); +var macros = require('../../lib/macro-manager'); + +var deviceMock; + +var CONFIG_HELLO_WORLD = [{ + name: 'hello world', + sequence: [ + ['say', 'hello'], + ['say', 'world'], + ], +}]; + +var CONFIG_DELAYED_HELLO_WORLD = [{ + name: 'hello world', + defaultDelay: 42, + sequence: [ + ['say', 'hello'], + ['say', 'world'], + ], +}]; + +var CONFIG_HELLO_STATES = [{ + name: 'hello!', + provides: 'hello', + sequence: [ + ['say', 'hello'], + ], +}, { + name: 'world!', + requires: 'hello', + sequence: [ + ['say', 'world'], + ], +}, { + name: 'goodbye!', + resets: 'hello', + sequence: [ + ['say', 'goodbye'], + ], +}, { + name: 'something', + resets: 'world', + sequence: [ + ['say', 'goodbye'], + ], +}]; + +var CONFIG_HELLO = [ + { + name: 'hello', + sequence: [ + ['say', 'hello'], + ], + }, { + name: 'world', + hidden: false, + sequence: [ + ['say', 'world'], + ], + }, { + name: '!', + hidden: true, + sequence: [ + ['say', '!'], + ], + }, +]; + +var CONFIG_DELEGATE = [ + { + name: 'exclamationmark', + hidden: true, + provides: 'worldIsGreeted', + sequence: [ + ['say', '!'], + ], + }, { + name: 'world', + hidden: true, + sequence: [ + ['say', 'world'], + ['call', 'exclamationmark'], + ], + }, { + name: 'hello', + hidden: false, + sequence: [ + ['say', 'hello'], + ['call', 'world'], + ], + }, +]; + +var CONFIG_MULTI_DELEGATE = [ + { + name: 'hello', + hidden: true, + sequence: [ + ['say', 'hello'], + ], + }, { + name: 'world', + hidden: true, + sequence: [ + ['say', 'world'], + ], + }, { + name: 'exclamationmark', + hidden: true, + sequence: [ + ['say', '!'], + ], + }, { + name: 'multi delegate', + sequence: [ + ['call', 'hello'], + ['call', 'world'], + ['call', 'exclamationmark'], + ], + }, +]; + +var CONFIG_360 = [{ + name: 'Play XBOX 360', + sequence: [ + ['SonyTV', 'Power'], + ['SonyTV', 'Xbox360'], + ['Yamaha', 'Power'], + ['Yamaha', 'Xbox360'], + ['Xbox360', 'Power'], + ], +}]; + +var CONFIG_DELAY_500 = [{ + name: 'macro with delay', + sequence: [ + ['delay', 500], + ['say', 'yay'], + ], +}]; + +var CONFIG_UNKNOWN_DEVICE = [{ + name: 'dont care', + sequence: [ + ['reject', 'a'], + ], +}]; + +function createDeviceMock(deviceName) { + return { + operations: [], + expectedArgs: null, + name: deviceName, + call: function (done, args) { + this.operations.push(args); + done(); + }, + validateArguments: function (args) { + if (this.expectedArgs !== null) { + if (args.length !== this.expectedArgs.length) { + return 'wrong argument count' + args.length; + } + if (args !== this.expectedArgs) { + return 'illegal arguments'; + } + } + if (args[0] !== this.name) { + return 'not for me!'; + } + return null; + }, + }; +} + +describe('macros', function () { + var clock; + var spy; + + function resetMockAndFixture() { + deviceMock.expectedArgs = null; + deviceMock.operations = []; + macros.resetConfiguration(); + macros.registerDevice(deviceMock); + } + + before(function () { + deviceMock = createDeviceMock('say'); + spy = sinon.spy(deviceMock, 'call'); + }); + + describe('configuration', function () { + beforeEach(function () { + resetMockAndFixture(); + }); + + it('should reject a macro with a command for a unknown device', function () { + assert.throws(function () {macros.init(CONFIG_UNKNOWN_DEVICE);}, Error); + }); + + it('should accept a macro with a command for a unknown device if catch all exists and accepts', + function () { + var myNewDevice = createDeviceMock('remotes'); + myNewDevice.validateArguments = function (args) { + var deviceName = args[0]; + var knownDevices = { SonyTV: true, Xbox360: true, Yamaha: true }; + if (!knownDevices[deviceName]) { + return 'remote not known.'; + } + return null; + }; + macros.registerDevice(myNewDevice); + macros.init(CONFIG_360); + }); + + it('should reject a macro with a invalid argument count for', function () { + deviceMock.expectedArgs = ['shu', 'bidu']; + assert.throws(function () {macros.init(CONFIG_HELLO_WORLD);}, Error); + }); + + it('should reject a macro with a invalid argument content', function () { + deviceMock.expectedArgs = ['hoo']; + assert.throws(function () {macros.init(CONFIG_HELLO_WORLD);}, Error); + }); + + it('should accept a macro with valid or unchecked arguments', function () { + macros.init(CONFIG_HELLO_WORLD); + }); + }); + + describe('builtin delay', function () { + beforeEach(function () { + resetMockAndFixture(); + clock = sinon.useFakeTimers(); + spy.reset(); + }); + + afterEach(function () { + clock.restore(); + }); + + it('should use delay when encountering a delay', function (done) { + macros.init(CONFIG_DELAY_500); + macros.execute(CONFIG_DELAY_500[0].name); + + assert.equal(spy.called, false); + + // not enough time has passed, stub should not have been called + clock.tick(250); + assert.equal(spy.called, false); + + // enough time has now passed, macro should have tried to execute next command + clock.tick(250); + assert.equal(spy.called, true); + done(); + }); + + it('should use standard delay of 50 when no delay is configured', function (done) { + macros.init(CONFIG_HELLO_WORLD); + macros.execute(CONFIG_HELLO_WORLD[0].name); + + // first macro step has no delay + assert(spy.calledOnce); + + // not enough time has passed, to reach the second step + clock.tick(49); + assert(spy.calledOnce); + + // enough time has now passed, macro should have tried to execute second command + clock.tick(1); + assert(spy.calledTwice); + done(); + }); + + it('should use modified delay of 42 when configured to do so', function (done) { + macros.init(CONFIG_DELAYED_HELLO_WORLD); + macros.execute(CONFIG_DELAYED_HELLO_WORLD[0].name); + + // first macro step has no delay + assert(spy.calledOnce); + + // not enough time has passed, to reach the second step + clock.tick(41); + assert(spy.calledOnce); + + // enough time has now passed, macro should have tried to execute second command + clock.tick(1); + assert(spy.calledTwice); + done(); + }); + }); + + describe('devices', function () { + it('should accept registrations', function () { + macros.registerDevice(deviceMock); + }); + }); + + describe('execution', function () { + beforeEach(function () { + resetMockAndFixture(); + clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + }); + + it('should accept a macro name for execution', function () { + macros.init(CONFIG_HELLO_WORLD); + macros.execute('hello world'); + clock.tick(250); + assert.deepEqual(deviceMock.operations, + [['say', 'hello'], ['say', 'world']]); + }); + + it('should call other macros daisychained', function () { + macros.init(CONFIG_DELEGATE); + macros.execute('hello'); + clock.tick(250); + assert.deepEqual(deviceMock.operations, + [['say', 'hello'], ['say', 'world'], ['say', '!']]); + }); + + it('should call other macros composed', function () { + macros.init(CONFIG_MULTI_DELEGATE); + macros.execute('multi delegate'); + clock.tick(250); + assert.deepEqual(deviceMock.operations, + [['say', 'hello'], ['say', 'world'], ['say', '!']]); + }); + }); + + describe('application support', function () { + before(function () { + resetMockAndFixture(); + }); + + it('should provide a list of macro names, that can be shown in a UI', function () { + macros.init(CONFIG_HELLO); + assert.deepEqual(macros.getGuiMacroLabels(), ['hello', 'world']); + }); + + it('should provide information if a macro is defined for a given name', function () { + macros.init(CONFIG_HELLO_WORLD); + assert.equal(macros.isMacroDefined('hello world'), true); + assert.equal(macros.isMacroDefined('Slartibartfass'), false); + }); + }); + + describe('state', function () { + describe('set, reset and query', function () { + beforeEach(function () { + resetMockAndFixture(); + clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + }); + + it('should deliver current states as key value map', function () { + macros.init(CONFIG_HELLO_STATES); + assert.deepEqual(macros.getCurrentStates(), { hello: 'clear', world: 'clear' }); + }); + + it('should set state after execution', function () { + macros.init(CONFIG_HELLO_STATES); + assert.deepEqual(macros.getCurrentStates(), + { hello: 'clear', world: 'clear' }, 'not called'); + macros.execute(CONFIG_HELLO_STATES[0].name); + clock.tick(51); + assert.deepEqual(macros.getCurrentStates(), + { hello: 'set', world: 'clear' }, 'macro called'); + }); + + it('should reset state after execution', function () { + macros.init(CONFIG_HELLO_STATES); + macros.execute(CONFIG_HELLO_STATES[0].name); + clock.tick(51); + assert.deepEqual(macros.getCurrentStates(), { hello: 'set', world: 'clear' }, + 'state set called'); + macros.execute(CONFIG_HELLO_STATES[2].name); + clock.tick(51); + assert.deepEqual(macros.getCurrentStates(), { hello: 'clear', world: 'clear' }, + 'state reset called'); + }); + + it('should set state in a called macro', function () { + macros.init(CONFIG_DELEGATE); + macros.execute(CONFIG_DELEGATE[2].name); + clock.tick(151); + assert.deepEqual(macros.getCurrentStates(), { worldIsGreeted: 'set' }, + 'state set called'); + }); + }); + + describe('dependency call', function () { + beforeEach(function () { + resetMockAndFixture(); + clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + }); + + it.skip('should call macro if state is requested', function () { + macros.init(CONFIG_HELLO_STATES); + macros.execute(CONFIG_HELLO_STATES[1].name); + clock.tick(51); + assert.deepEqual(deviceMock.operations, + [['say', 'hello'], ['say', 'world']], 'calls are not done as expected'); + assert.deepEqual(macros.getCurrentStates(), { hello: true, world: false }, + 'states are not set as required'); + }); + }); + }); +}); + +// vim: expandtab tabstop=8 softtabstop=2 shiftwidth=2 diff --git a/test/lib/macros.js b/test/lib/macros.js deleted file mode 100644 index 385107f..0000000 --- a/test/lib/macros.js +++ /dev/null @@ -1,76 +0,0 @@ -var macros = require('../../lib/macros'); -var assert = require('assert'); -var sinon = require('sinon'); - -// config fixture -var config = { - macros: { - 'Play Xbox 360': [ - ['SonyTV', 'Power'], - ['SonyTV', 'Xbox360'], - ['Yamaha', 'Power'], - ['Yamaha', 'Xbox360'], - ['Xbox360', 'Power'], - ], - 'Macro With Delay': [ - ['delay', 500], - ['Yamaha', 'Power'], - ], - }, -}; - -describe('macros', function () { - var lircNode; - var clock; - var stub; - - beforeEach(function (done) { - clock = sinon.useFakeTimers(); - - lircNode = {}; - lircNode.irsend = {}; - lircNode.irsend.send_once = function () {}; - - stub = sinon.stub(lircNode.irsend, 'send_once', function (remote, command, cb) { - cb(); - }); - - done(); - }); - - afterEach(function (done) { - clock.restore(); - - done(); - }); - - describe('exec', function () { - it('should call lircNode.irsend.send_once when executing a macro', function () { - macros.exec(config.macros['Play Xbox 360'], lircNode); - - assert.equal(stub.called, true); - }); - - it('should delay when encountering a delay', function () { - macros.exec(config.macros['Macro With Delay'], lircNode); - - assert.equal(stub.called, false); - - // not enough time has passed, stub should not have been called - clock.tick(250); - assert.equal(stub.called, false); - - // enough time has now passed, macro should have tried to execute next command - clock.tick(250); - assert.equal(stub.called, true); - }); - - it('should call send_once once per command', function () { - macros.exec(config.macros['Play Xbox 360'], lircNode); - // wait enough time - clock.tick(500); - - assert.equal(stub.callCount, 5); - }); - }); -}); diff --git a/test/lirc_web.js b/test/lirc_web.js index abe8a94..99d1089 100644 --- a/test/lirc_web.js +++ b/test/lirc_web.js @@ -9,15 +9,30 @@ var gpioMock = require('./lib/wiring-pi-mock'); var gpio = require('../lib/gpio'); describe('lirc_web', function () { - before(function () { + beforeEach(function () { + var deviceMock = require('./lib/lirc').getDeviceMock(require('./fixtures/remotes')); + var lirc = require('../lib/lirc'); + var macros = require('../lib/macro-manager'); + var config = require('./fixtures/config'); + macros.resetConfiguration(); gpio.init(configFixture.gpios); + macros.registerDevice(gpio); + lirc.overrideHardware(deviceMock); + lirc.init(config); + macros.registerDevice(lirc); + macros.init(config.macros); }); + describe('routes', function () { // Root route it('should have an index route "/"', function (done) { assert(request(app).get('/').expect(200, done)); }); + it('should have GET route for a refresh of configuration', function (done) { + assert(request(app).get('/refresh').expect(302, done)); + }); + // JSON API it('should have GET route for JSON list of macros', function (done) { assert(request(app).get('/macros.json').expect(200, done)); @@ -31,18 +46,10 @@ describe('lirc_web', function () { assert(request(app).get('/remotes/Xbox360.json').expect(200, done)); }); - it('should return 404 for unknown remote', function (done) { + it('should give a 404 for a JSON list of commands for unknown remote', function (done) { assert(request(app).get('/remotes/DOES_NOT_EXIST.json').expect(404, done)); }); - it('should have GET route for JSON list of commands for macro', function (done) { - assert(request(app).get('/macros/Play%20Xbox%20360.json').expect(200, done)); - }); - - it('should properly handle macros with / in them', function (done) { - assert(request(app).get('/macros/Listen%20to%20Music%20%2F%20Jams.json').expect(200, done)); - }); - it('should have GET route for JSON list of gpio pins', function (done) { assert(request(app).get('/gpios.json').expect(200, done)); }); @@ -65,6 +72,10 @@ describe('lirc_web', function () { assert(request(app).post('/macros/Play%20Xbox%20360').expect(200, done)); }); + it('should properly handle macros with / in them', function (done) { + assert(request(app).post('/macros/Listen%20to%20Music%20%2F%20Jams').expect(200, done)); + }); + // Sending GPIO change requests it('should have POST route for gpio toggle', function (done) { var before = 0; From 99df3263865b6d1255514d098307ea8ee241fcad Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sat, 30 Jan 2016 13:10:13 +0100 Subject: [PATCH 02/27] fake gpio to enable dev on mac and win --- lib/gpio.js | 4 ++-- package.json | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/gpio.js b/lib/gpio.js index cf0a955..ce79d2d 100644 --- a/lib/gpio.js +++ b/lib/gpio.js @@ -1,6 +1,6 @@ var DEVICE_NAME = 'gpio'; -var wpi = require('wiring-pi'); -// var wpi = require('../test/lib/wiring-pi-mock.js'); +// var wpi = require('wiring-pi'); +var wpi = require('../test/lib/wiring-pi-mock.js'); var gpios = null; function updatePinStates() { diff --git a/package.json b/package.json index f1ff678..5e9b829 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "lirc_node": "0.0.4", "lodash": "^3.10.1", "morgan": "^1.6.1", - "wiring-pi": "^2.1.0", "swig": "^1.4.2" }, "devDependencies": { From 483e8dbc67e7632344c5b4ce4e46062b5403ff7e Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sat, 30 Jan 2016 13:41:27 +0100 Subject: [PATCH 03/27] remove project name from dev environment config ignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7fd8018..03a9f3b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ npm-debug.log server.cert server.key .idea -lirc_web.iml +*.iml From 6908d2564fb49aa5b5415cd7f36469698e5dc9ef Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sun, 31 Jan 2016 22:37:01 +0100 Subject: [PATCH 04/27] preparation to switch to file based gpio implementation --- app.js | 16 ++++---- lib/gpio-la-mock.js | 22 +++++++++++ lib/gpio-la-wiringpi.js | 25 ++++++++++++ lib/gpio.js | 76 ++++++++++++++++++++++-------------- package.json | 1 + test/lib/gpio.js | 79 ++++++++++++++++++-------------------- test/lib/wiring-pi-mock.js | 20 ---------- test/lirc_web.js | 2 +- 8 files changed, 144 insertions(+), 97 deletions(-) create mode 100644 lib/gpio-la-mock.js create mode 100644 lib/gpio-la-wiringpi.js delete mode 100644 test/lib/wiring-pi-mock.js diff --git a/app.js b/app.js index 9f335ea..d3aaf76 100644 --- a/app.js +++ b/app.js @@ -77,7 +77,7 @@ function overrideConfigurationForDebugOrDevelopment() { console.log('we are in test mode!'); lircTest = require('./test/lib/lirc'); lircTest.replaceLircByMock(); - gpio.overrideWiringPi(require('./test/lib/wiring-pi-mock')); + gpio.setGpioLibrary(require('./lib/gpio-la-mock')); config = require('./test/fixtures/config.json'); hasServerPortConfig = false; hasSSLConfig = false; @@ -149,8 +149,9 @@ app.get('/remotes/:remote.json', function (req, res) { function respondWithGpioState(res) { if (config.gpios) { - gpio.updatePinStates(); - res.json(config.gpios); + gpio.updatePinStates(function (result) { + res.json(result); + }, config.gpios); } else { res.send(404); } @@ -189,10 +190,11 @@ app.post('/remotes/:remote/:command/send_stop', function (req, res) { // toggle /gpios/:gpio_pin app.post('/gpios/:gpio_pin', function (req, res) { - var newValue = gpio.togglePin(req.params.gpio_pin); - res.setHeader('Cache-Control', 'no-cache'); - res.json(newValue); - res.end(); + gpio.togglePin(function (result) { + res.setHeader('Cache-Control', 'no-cache'); + res.json(result); + res.end(); + }, req.params.gpio_pin); }); diff --git a/lib/gpio-la-mock.js b/lib/gpio-la-mock.js new file mode 100644 index 0000000..a98ef22 --- /dev/null +++ b/lib/gpio-la-mock.js @@ -0,0 +1,22 @@ +module.exports = { + emulatedPins: new Array(50), + getPinStateById: function (callback, pin) { + callback(this.emulatedPins[pin]); + }, + setPinByIdToState: function (callback, pin, state) { + this.emulatedPins[pin] = state; + callback(this.emulatedPins[pin]); + }, + init: function (callback) { + this.initPinsWith(0); + if (callback) { + callback(); + } + }, + initPinsWith: function (value) { + var i; + for (i = 0; i < this.emulatedPins.length; i++) { + this.emulatedPins[i] = value; + } + }, +}; diff --git a/lib/gpio-la-wiringpi.js b/lib/gpio-la-wiringpi.js new file mode 100644 index 0000000..15e107d --- /dev/null +++ b/lib/gpio-la-wiringpi.js @@ -0,0 +1,25 @@ +// var wpi = require('wiring-pi'); +var wpi = { + setup: function () {}, + digitalRead: function () {}, + digitalWrite: function () {}, +}; + +exports.init = function (callback) { + wpi.setup('gpio'); + if (callback) callback(); +}; + +exports.getPinStateById = function (callback, pinId) { + var result = wpi.digitalRead(pinId); + if (callback) { + callback(result); + } +}; + +exports.setPinByIdToState = function (callback, pinId, newState) { + wpi.digitalWrite(pinId, newState); + if (callback) { + callback(newState); + } +}; diff --git a/lib/gpio.js b/lib/gpio.js index ce79d2d..95fd789 100644 --- a/lib/gpio.js +++ b/lib/gpio.js @@ -1,28 +1,44 @@ var DEVICE_NAME = 'gpio'; -// var wpi = require('wiring-pi'); -var wpi = require('../test/lib/wiring-pi-mock.js'); +var gpioLibrary = require('./gpio-la-mock'); var gpios = null; -function updatePinStates() { +function updatePinStates(callback, pinsToRead) { + var toCollect = pinsToRead.length; + var collector = function () { + if (--toCollect === 0) { + if (callback) { + callback(pinsToRead); + } + } + }; + var i; - var gpio; - if (gpios) { - for (i = 0; i < gpios.length; i++) { - gpio = gpios[i]; - gpio.state = wpi.digitalRead(gpio.pin); + if (pinsToRead) { + for (i = 0; i < pinsToRead.length; i++) { + (function (pinIdx) { + var pin = pinsToRead[pinIdx]; + gpioLibrary.getPinStateById(function (result) { + pin.state = result; + collector(); + }, pin.pin); + })(i); } } } -function togglePin(pinId) { +function togglePin(callback, pinId) { var numericPinId = parseInt(pinId, 10); - var currentState = wpi.digitalRead(numericPinId); - var newState = currentState > 0 ? 0 : 1; - wpi.digitalWrite(numericPinId, newState); - return { - pin: numericPinId, - state: newState, - }; + gpioLibrary.getPinStateById(function (currentState) { + var newState = currentState > 0 ? 0 : 1; + gpioLibrary.setPinByIdToState(function (result) { + if (callback) { + callback({ + pin: numericPinId, + state: result, + }); + } + }, numericPinId, newState); + }, numericPinId); } function findElement(array, propertyName, propertyValue) { @@ -39,27 +55,31 @@ function getPinIdByName(pinName) { return gpioPin.pin; } -function setPin(pinName, newState) { +function setPin(callback, pinName, newState) { var numericPinId = getPinIdByName(pinName); - wpi.digitalWrite(numericPinId, newState); + gpioLibrary.setPinByIdToState(callback, numericPinId, newState); } -function init(configuration) { +function init(configuration, callback) { if (configuration) { gpios = configuration; - wpi.setup('gpio'); - updatePinStates(); + gpioLibrary.init(function () { + updatePinStates(function (result) { + gpios = result; + if (callback) { + callback(); + } + }, gpios); + }, configuration); } } -function overrideWiringPi(wpiReplacement) { - var origWpi = wpi; - wpi = wpiReplacement; - return origWpi; +function setGpioLibrary(newGpioLibrary) { + gpioLibrary = newGpioLibrary; } function call(done, command) { - setPin(command[1], command[2]); + setPin(function () {}, command[1], command[2]); done(); } @@ -84,10 +104,10 @@ function validateArguments(args) { module.exports = { name: DEVICE_NAME, + setGpioLibrary: setGpioLibrary, init: init, - setPin: setPin, - overrideWiringPi: overrideWiringPi, updatePinStates: updatePinStates, + setPin: setPin, togglePin: togglePin, validateArguments: validateArguments, call: call, diff --git a/package.json b/package.json index 5e9b829..d43224a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "lirc_node": "0.0.4", "lodash": "^3.10.1", "morgan": "^1.6.1", + "gpio": "^0.2.0", "swig": "^1.4.2" }, "devDependencies": { diff --git a/test/lib/gpio.js b/test/lib/gpio.js index 4c44ff9..b692df1 100644 --- a/test/lib/gpio.js +++ b/test/lib/gpio.js @@ -1,72 +1,69 @@ var gpio = require('../../lib/gpio'); var assert = require('assert'); -var gpioProbe = require('./wiring-pi-mock.js'); +var gpioProbe = require('./../../lib/gpio-la-mock.js'); var config = [ { name: 'a', pin: 47, state: 0 }, { name: 'b', pin: 11, state: 0 }]; describe('gpio', function () { - var realWpi; before(function () { gpioProbe.initPinsWith(0); - realWpi = gpio.overrideWiringPi(gpioProbe); + gpio.setGpioLibrary(gpioProbe); gpio.init(config); }); - after(function () { - gpio.overrideWiringPi(realWpi); - }); - describe('updatePinStates', function () { - it('should update all pin states', function () { + it('should update all pin states', function (done) { gpioProbe.initPinsWith(1); - gpio.updatePinStates(); - assert.deepEqual( - config, - [{ name: 'a', pin: 47, state: 1 }, - { name: 'b', pin: 11, state: 1 }], - 'states are not updated properly'); + gpio.updatePinStates(function (result) { + assert.deepEqual( + result, + [{ name: 'a', pin: 47, state: 1 }, + { name: 'b', pin: 11, state: 1 }], + 'states are not updated properly'); + done(); + }, config); }); }); describe('togglePin', function () { - it('should change active pin to inactive', function () { - var res; + it('should change active pin to inactive', function (done) { gpioProbe.initPinsWith(1); - res = gpio.togglePin(47); - assert.deepEqual( - res, - { pin: 47, state: 0 }, - 'pin has not changed its state'); + gpio.togglePin(function (res) { + assert.deepEqual( + res, + { pin: 47, state: 0 }, + 'pin has not changed its state'); + done(); + }, 47); }); - it('should change inactive pin to active', function () { - var res; + it('should change inactive pin to active', function (done) { gpioProbe.initPinsWith(0); - res = gpio.togglePin(47); - assert.deepEqual( - res, - { pin: 47, state: 1 }, - 'pin has not changed its state'); + gpio.togglePin(function (res) { + assert.deepEqual( + res, + { pin: 47, state: 1 }, + 'pin has not changed its state'); + done(); + }, 47); }); }); describe('setPin', function () { - it('should set a pin by name to the given value', function () { + it('should set a pin by name to the given value', function (done) { gpioProbe.initPinsWith(0); - gpio.setPin('a', 1); - assert.equal(gpioProbe.emulatedPins[47], 1); - gpio.setPin('a', 1); - assert.equal(gpioProbe.emulatedPins[47], 1); - gpio.setPin('a', 0); - assert.equal(gpioProbe.emulatedPins[47], 0); - }); - }); - - describe('init', function () { - it('should initialize the wiring_pi library to use gpio address schema', function () { - assert.equal(gpioProbe.schema, 'gpio'); + gpio.setPin(function () { + assert.equal(gpioProbe.emulatedPins[47], 1); + gpio.setPin(function () { + assert.equal(gpioProbe.emulatedPins[47], 1); + gpio.setPin(function () { + assert.equal(gpioProbe.emulatedPins[47], 0); + done(); + }, 'a', 0); + }, 'a', 1); + }, 'a', 1); }); }); }); diff --git a/test/lib/wiring-pi-mock.js b/test/lib/wiring-pi-mock.js deleted file mode 100644 index 76e9371..0000000 --- a/test/lib/wiring-pi-mock.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - emulatedPins: new Array(50), - schema: null, - digitalRead: function (pin) { - return this.emulatedPins[pin]; - }, - digitalWrite: function (pin, state) { - this.emulatedPins[pin] = state; - return this.emulatedPins[pin]; - }, - setup: function (schema) { - this.schema = schema; - }, - initPinsWith: function (value) { - var i; - for (i = 0; i < this.emulatedPins.length; i++) { - this.emulatedPins[i] = value; - } - }, -}; diff --git a/test/lirc_web.js b/test/lirc_web.js index 99d1089..f30a10f 100644 --- a/test/lirc_web.js +++ b/test/lirc_web.js @@ -5,7 +5,7 @@ var jsdom = require('jsdom'); var fs = require('fs'); var jquery = fs.readFileSync('node_modules/jquery/dist/jquery.js', 'utf-8'); var configFixture = require(__dirname + '/fixtures/config.json'); -var gpioMock = require('./lib/wiring-pi-mock'); +var gpioMock = require('./../lib/gpio-la-mock'); var gpio = require('../lib/gpio'); describe('lirc_web', function () { From e06f40d122f65877a33cff32179747a8a3dcc194 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sun, 31 Jan 2016 23:13:54 +0100 Subject: [PATCH 05/27] refactoring: remove lint warning --- lib/gpio.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/gpio.js b/lib/gpio.js index 95fd789..23613e0 100644 --- a/lib/gpio.js +++ b/lib/gpio.js @@ -2,6 +2,14 @@ var DEVICE_NAME = 'gpio'; var gpioLibrary = require('./gpio-la-mock'); var gpios = null; +function fetchPinByIndex(callback, pinsToRead, pinIdx) { + var pin = pinsToRead[pinIdx]; + gpioLibrary.getPinStateById(function (result) { + pin.state = result; + callback(); + }, pin.pin, callback); +} + function updatePinStates(callback, pinsToRead) { var toCollect = pinsToRead.length; var collector = function () { @@ -15,13 +23,7 @@ function updatePinStates(callback, pinsToRead) { var i; if (pinsToRead) { for (i = 0; i < pinsToRead.length; i++) { - (function (pinIdx) { - var pin = pinsToRead[pinIdx]; - gpioLibrary.getPinStateById(function (result) { - pin.state = result; - collector(); - }, pin.pin); - })(i); + fetchPinByIndex(collector, pinsToRead, i); } } } From 7a26ab9489360bbfa697719d622b032e54805a88 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Mon, 1 Feb 2016 19:08:55 +0100 Subject: [PATCH 06/27] initial implementation of 'gpio' lib abstraction --- lib/gpio-la-gpio.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 lib/gpio-la-gpio.js diff --git a/lib/gpio-la-gpio.js b/lib/gpio-la-gpio.js new file mode 100644 index 0000000..31d9e23 --- /dev/null +++ b/lib/gpio-la-gpio.js @@ -0,0 +1,35 @@ +var gpioLib = require('gpio'); + +function configurePin(callback, pinConfig) { + gpioLib.export(pinConfig.pin, { + direction: 'out', + }); +} + +exports.init = function (callback, config) { + var configsLeft = config.length(); + var i; + function collector() { + configsLeft -= 1; + if (configsLeft === 0) { + if (callback) callback(); + } + } + for (i = 0; i < config.length(); i++) { + configurePin(collector, config[i]); + } +}; + +exports.getPinStateById = function (callback, pinId) { + var result = pinId; // TODO gpio.readPin(pinId) or so + if (callback) { + callback(result); + } +}; + +exports.setPinByIdToState = function (callback, pinId, newState) { + // wpi.digitalWrite(pinId, newState); TODO + if (callback) { + callback(newState); + } +}; From 2a48e3b02d19027adc2c21142d05de90d2cec6f7 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Mon, 1 Feb 2016 22:16:23 +0100 Subject: [PATCH 07/27] implementation of filebased gpio finished --- app.js | 2 ++ lib/gpio-la-gpio.js | 24 ++++++++++++++++-------- lib/gpio-la-mock.js | 1 + lib/gpio-la-wiringpi.js | 2 ++ lib/gpio.js | 1 + 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app.js b/app.js index d3aaf76..950ae1a 100644 --- a/app.js +++ b/app.js @@ -81,6 +81,8 @@ function overrideConfigurationForDebugOrDevelopment() { config = require('./test/fixtures/config.json'); hasServerPortConfig = false; hasSSLConfig = false; + } else { + gpio.setGpioLibrary(require('./lib/gpio-la-gpio')); } } diff --git a/lib/gpio-la-gpio.js b/lib/gpio-la-gpio.js index 31d9e23..11a6a4e 100644 --- a/lib/gpio-la-gpio.js +++ b/lib/gpio-la-gpio.js @@ -1,13 +1,17 @@ var gpioLib = require('gpio'); +var pinStubs = {}; function configurePin(callback, pinConfig) { - gpioLib.export(pinConfig.pin, { + var stub = gpioLib.export(pinConfig.pin, { direction: 'out', + interval: 500, + ready: callback, }); + pinStubs['' + pinConfig.pin] = stub; } exports.init = function (callback, config) { - var configsLeft = config.length(); + var configsLeft = config.length; var i; function collector() { configsLeft -= 1; @@ -15,21 +19,25 @@ exports.init = function (callback, config) { if (callback) callback(); } } - for (i = 0; i < config.length(); i++) { + for (i = 0; i < config.length; i++) { configurePin(collector, config[i]); } }; exports.getPinStateById = function (callback, pinId) { - var result = pinId; // TODO gpio.readPin(pinId) or so + var result = pinStubs['' + pinId].value; if (callback) { callback(result); } }; exports.setPinByIdToState = function (callback, pinId, newState) { - // wpi.digitalWrite(pinId, newState); TODO - if (callback) { - callback(newState); - } + var stub = pinStubs['' + pinId]; + stub.set(newState, function () { + if (callback) { + callback(stub.value); + } + }); }; + +exports.name = 'gpio'; diff --git a/lib/gpio-la-mock.js b/lib/gpio-la-mock.js index a98ef22..d14f053 100644 --- a/lib/gpio-la-mock.js +++ b/lib/gpio-la-mock.js @@ -19,4 +19,5 @@ module.exports = { this.emulatedPins[i] = value; } }, + name: 'mock', }; diff --git a/lib/gpio-la-wiringpi.js b/lib/gpio-la-wiringpi.js index 15e107d..8803596 100644 --- a/lib/gpio-la-wiringpi.js +++ b/lib/gpio-la-wiringpi.js @@ -23,3 +23,5 @@ exports.setPinByIdToState = function (callback, pinId, newState) { callback(newState); } }; + +exports.name = 'wiring-pi'; diff --git a/lib/gpio.js b/lib/gpio.js index 23613e0..ddba435 100644 --- a/lib/gpio.js +++ b/lib/gpio.js @@ -65,6 +65,7 @@ function setPin(callback, pinName, newState) { function init(configuration, callback) { if (configuration) { gpios = configuration; + console.log('init ' + gpioLibrary.name); gpioLibrary.init(function () { updatePinStates(function (result) { gpios = result; From 057b89b5c0e2fed501c39f6c7786a347bc1504aa Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Mon, 1 Feb 2016 22:37:28 +0100 Subject: [PATCH 08/27] cleanup: log output removed --- lib/gpio.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/gpio.js b/lib/gpio.js index ddba435..23613e0 100644 --- a/lib/gpio.js +++ b/lib/gpio.js @@ -65,7 +65,6 @@ function setPin(callback, pinName, newState) { function init(configuration, callback) { if (configuration) { gpios = configuration; - console.log('init ' + gpioLibrary.name); gpioLibrary.init(function () { updatePinStates(function (result) { gpios = result; From 9d3668bd23703b187544f007fa3212efe5c797db Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Tue, 2 Feb 2016 08:20:10 +0100 Subject: [PATCH 09/27] introduce example init.d/osur script --- example_configs/init/osur | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 example_configs/init/osur diff --git a/example_configs/init/osur b/example_configs/init/osur new file mode 100755 index 0000000..a65f504 --- /dev/null +++ b/example_configs/init/osur @@ -0,0 +1,47 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: osur +# Required-Start: $lirc +# Required-Stop: $lirc +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts the open source universal remote web service. +# Description: osur is a universal remote web interface. +# +### END INIT INFO + +. /lib/lsb/init-functions + +NODE=/usr/local/bin/node +HOME=/home/pi +OSUR=$HOME/osur/app.js +LOGFILE=/var/log/osur.log +USER=pi +GROUP=gpio + +test -f $NODE || exit 0 +test -f $OSUR || exit 0 + +case "$1" in + start) + log_daemon_msg "Starting open source remote control web interface:" + cd $HOME + start-stop-daemon --start --chuid $USER:$GROUP --exec $NODE $OSUR >>$LOGFILE 2>&1 & + log_end_msg $? + ;; + stop) + log_daemon_msg "Stopping execution daemon: osur" + start-stop-daemon --stop --exec $NODE $OSUR + log_end_msg $? + ;; + reload|force-reload|restart) + $0 stop + sleep 1 + $0 start $2 + ;; + *) + echo "Usage: /etc/init.d/osur {start|stop|reload|restart|force-reload}" + exit 1 +esac + +exit 0 From e399ed42bc6061264bcdeff05ed30c232f44fb3b Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Tue, 2 Feb 2016 21:14:45 +0100 Subject: [PATCH 10/27] refactoring: unique format --- example_configs/config.json | 80 +++++++++++++++++++++---------------- test/fixtures/config.json | 41 +++++++++++-------- 2 files changed, 71 insertions(+), 50 deletions(-) diff --git a/example_configs/config.json b/example_configs/config.json index 9515b65..cd2c78d 100644 --- a/example_configs/config.json +++ b/example_configs/config.json @@ -1,10 +1,10 @@ { - "server" : { - "port" : 3000, - "ssl" : false, - "ssl_cert" : "/home/pi/lirc_web/server.cert", - "ssl_key" : "/home/pi/lirc_web/server.key", - "ssl_port" : 3001 + "server": { + "port": 3000, + "ssl": false, + "ssl_cert": "/home/pi/lirc_web/server.cert", + "ssl_key": "/home/pi/lirc_web/server.key", + "ssl_port": 3001 }, "repeaters": { "SonyTV": { @@ -12,23 +12,29 @@ "VolumeDown": true } }, - "macros": { - "Xbox360": [ - [ "gpio", "TV", 1], - [ "gpio", "Receiver", 1], - [ "gpio", "Xbox", 1], - [ "SonyTV", "Power" ], - [ "SonyTV", "Xbox360" ], - [ "Yamaha", "Power" ], - [ "Yamaha", "Xbox360" ], - [ "Xbox360", "Power" ] - ], - "all off": [ - [ "gpio", "TV", 0], - [ "gpio", "Receiver", 0], - [ "gpio", "Xbox", 0] - ] - }, + "macros": [ + { + "name": "Xbox360", + "sequence": [ + [ "gpio", "TV", 1 ], + [ "gpio", "Receiver", 1 ], + [ "gpio", "Xbox", 1 ], + [ "SonyTV", "Power" ], + [ "SonyTV", "Xbox360" ], + [ "Yamaha", "Power" ], + [ "Yamaha", "Xbox360" ], + [ "Xbox360", "Power" ] + ] + }, + { + "name": "all off", + "sequence": [ + [ "gpio", "TV", 0 ], + [ "gpio", "Receiver", 0 ], + [ "gpio", "Xbox", 0 ] + ] + } + ], "commandLabels": { "Yamaha": { "Xbox360": "Xbox 360", @@ -37,20 +43,26 @@ } }, "remoteLabels": { - "Xbox360": "Xbox 360" + "Xbox360": "Xbox 360" }, "blacklists": { - "Yamaha": [ - "AUX2", - "AUX3" - ] + "Yamaha": [ + "AUX2", + "AUX3" + ] }, "gpios": [ - {"name": "Receiver", - "pin": 19}, - {"name": "TV", - "pin": 16}, - {"name": "Xbox", - "pin": 13} + { + "name": "Receiver", + "pin": 19 + }, + { + "name": "TV", + "pin": 16 + }, + { + "name": "Xbox", + "pin": 13 + } ] } diff --git a/test/fixtures/config.json b/test/fixtures/config.json index 21cb847..e3e22de 100644 --- a/test/fixtures/config.json +++ b/test/fixtures/config.json @@ -9,30 +9,33 @@ { "name": "Play Xbox 360", "sequence": [ - [ "gpio", "TV", 1], - [ "gpio", "Receiver", 1], - [ "gpio", "Xbox", 1], + [ "gpio", "TV", 1 ], + [ "gpio", "Receiver", 1 ], + [ "gpio", "Xbox", 1 ], [ "SonyTV", "Power" ], [ "SonyTV", "Xbox360" ], [ "Yamaha", "Power" ], [ "Yamaha", "Xbox360" ], [ "Xbox360", "Power" ] ] - },{ + }, + { "name": "Listen to Music / Jams", "sequence": [ - [ "gpio", "Receiver", 1], + [ "gpio", "Receiver", 1 ], [ "Yamaha", "Power" ], [ "Yamaha", "AirPlay" ] ] - },{ + }, + { "name": "all off", "sequence": [ - [ "gpio", "TV", 0], - [ "gpio", "Receiver", 0], - [ "gpio", "Xbox", 0] + [ "gpio", "TV", 0 ], + [ "gpio", "Receiver", 0 ], + [ "gpio", "Xbox", 0 ] ] - },{ + }, + { "name": "Macro With Delay", "sequence": [ [ "delay", 500 ], @@ -62,11 +65,17 @@ ] }, "gpios": [ - {"name": "Receiver", - "pin": 19}, - {"name": "TV", - "pin": 16}, - {"name": "Xbox", - "pin": 13} + { + "name": "Receiver", + "pin": 19 + }, + { + "name": "TV", + "pin": 16 + }, + { + "name": "Xbox", + "pin": 13 + } ] } From a86b7670f67ef2d433ff9b0aec48e8f0ff884128 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Tue, 2 Feb 2016 21:30:17 +0100 Subject: [PATCH 11/27] mention new features in example config, Changelog and Readme --- CHANGELOG.md | 2 ++ README.md | 58 ++++++++++++++++++++----------------- example_configs/config.json | 11 +++++++ 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 436acaf..e613df9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ As of `v0.1.0`, this project adheres to [Semantic Versioning](http://semver.org/ ## Unreleased * Adds support for gpio controlled devices (thanks @OvisMaximus) * Improves log output on used configuration (thanks @OvisMaximus) +* Allow macros call macros (thanks @OvixMaximus) +* Allow to define a default delay for a macro (thanks @OvisMaximus) ## [0.2.4] - 2016-01-13 diff --git a/README.md b/README.md index ad8a935..cd7747f 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ These are the available configuration options: #### Example config.json: - { "server" : { "port" : 3000, @@ -61,31 +60,38 @@ These are the available configuration options: "VolumeDown": true } }, - "macros": { - "Play Xbox 360": [ - [ "gpio", "TV", 1], - [ "gpio", "Receiver", 1], - [ "gpio", "Xbox", 1], - [ "SonyTV", "Power" ], - [ "delay", 500 ], - [ "SonyTV", "Xbox360" ], - [ "Yamaha", "Power" ], - [ "delay", 250 ], - [ "Yamaha", "Xbox360" ], - [ "Xbox360", "Power" ] - ], - "Listen to Music": [ - [ "gpio", "Receiver", 1], - [ "Yamaha", "Power" ], - [ "delay", 500 ], - [ "Yamaha", "AirPlay" ] - ], - "all off": [ - [ "gpio", "TV", 0], - [ "gpio", "Receiver", 0], - [ "gpio", "Xbox", 0] - ], - }, + "macros": [ + { + "name": "Xbox360", + "sequence": [ + [ "gpio", "TV", 1 ], + [ "gpio", "Receiver", 1 ], + [ "gpio", "Xbox", 1 ], + [ "SonyTV", "Power" ], + [ "SonyTV", "Xbox360" ], + [ "Yamaha", "Power" ], + [ "Yamaha", "Xbox360" ], + [ "Xbox360", "Power" ] + ]}, + { + "name": "lights off", + "defaultDelay": 40, + "sequence": [ + [ "Lightcontrol", "C01off"], + [ "Lightcontrol", "C02off"], + [ "Lightcontrol", "C03off"] + ]}, + { + "name": "all off", + "defaultDelay": 20, + "sequence": [ + [ "call", "lights off"], + [ "gpio", "TV", 0 ], + [ "gpio", "Receiver", 0 ], + [ "gpio", "Xbox", 0 ] + ] + } + ], "commandLabels": { "Yamaha": { "Power": "Power", diff --git a/example_configs/config.json b/example_configs/config.json index cd2c78d..8a1f002 100644 --- a/example_configs/config.json +++ b/example_configs/config.json @@ -26,9 +26,20 @@ [ "Xbox360", "Power" ] ] }, + { + "name": "lights off", + "defaultDelay": 40, + "sequence": [ + [ "Lightcontrol", "C01off"], + [ "Lightcontrol", "C02off"], + [ "Lightcontrol", "C03off"] + ] + }, { "name": "all off", + "defaultDelay": 20, "sequence": [ + [ "call", "lights off"], [ "gpio", "TV", 0 ], [ "gpio", "Receiver", 0 ], [ "gpio", "Xbox", 0 ] From 2f1d1c0b4748e399f271f170a3746efa086a0b02 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Wed, 3 Feb 2016 08:18:22 +0100 Subject: [PATCH 12/27] proceed with implementation of dependency resolving --- lib/macro-manager.js | 45 +++++++++++-- lib/macro.js | 8 ++- test/lib/macro-manager.js | 138 ++++++++++++++++++++++++++++---------- 3 files changed, 146 insertions(+), 45 deletions(-) diff --git a/lib/macro-manager.js b/lib/macro-manager.js index 4d0bf94..5a93d96 100644 --- a/lib/macro-manager.js +++ b/lib/macro-manager.js @@ -14,23 +14,39 @@ function State(name, value) { this.name = name; this.value = value; this.providingMacro = null; + this.toBeCalledOnRisingEdge = []; + this.setProvidingMacro = function (macro) { this.providingMacro = macro; }; + + this.registerWaitingMacro = function (waitingDoneCallback) { + this.toBeCalledOnRisingEdge.push(waitingDoneCallback); + }; + + this.notifyWaitingMacros = function () { + var callback; + while (this.toBeCalledOnRisingEdge.length > 0) { + callback = this.toBeCalledOnRisingEdge.shift(); + callback(); + } + }; + this.ensureIsSet = function (done) { if (this.value === constants.STATE_NOT_SET) { + this.value = constants.STATE_PENDING_SET; + this.registerWaitingMacro(done); + this.providingMacro.execute( + function () { + stateInstance.notifyWaitingMacros(); + } + ); + } else if (this.value === constants.STATE_PENDING_SET) { this.registerWaitingMacro(done); - this.providingMacro(function () {this.notifyWaitingMacros();}); - } else if (stateInstance.value === constants.STATE_PENDING_SET) { - // warten } else { done(); } }; - - this.notifyWaitingMacros = function () { - - }; } function StateManager() { @@ -55,6 +71,10 @@ function StateManager() { state.value = stateValue; }; + this.getStateByName = function (name) { + return this.stateByName[name]; + }; + this.getStateSummary = function () { var summary = {}; var stateName; @@ -123,6 +143,15 @@ function registerState(stateName) { virtualStates.registerState(stateName); } +function registerStateProvider(stateName, macro) { + virtualStates.registerStateProvider(stateName, macro); +} + +function requestStateIsSet(callback, stateName) { + var state = virtualStates.getStateByName(stateName); + state.ensureIsSet(callback); +} + function setStateByName(stateName, newState) { virtualStates.setStateByName(stateName, newState); } @@ -136,7 +165,9 @@ function getDeviceByName(deviceName) { } manager = { + registerStateProvider: registerStateProvider, registerState: registerState, + requestStateIsSet: requestStateIsSet, setStateByName: setStateByName, getCatchAllDevice: getCatchAllDevice, getDeviceByName: getDeviceByName, diff --git a/lib/macro.js b/lib/macro.js index b64de57..6d7f6e0 100644 --- a/lib/macro.js +++ b/lib/macro.js @@ -125,7 +125,7 @@ function Macro(config, macroManager) { this.registerStates = function () { if (this.isProvidingState()) { - macroManager.registerState(config.provides); + macroManager.registerStateProvider(config.provides, this); } if (this.isRequiringState()) { macroManager.registerState(config.requires); @@ -137,7 +137,11 @@ function Macro(config, macroManager) { this.execute = function (done) { this.callersCallback = done; - this.macroClosureChain(); + if (this.isRequiringState()) { + macroManager.requestStateIsSet(this.macroClosureChain, config.requires); + } else { + this.macroClosureChain(); + } }; this.macroClosureChain = this.createClosureChain(); diff --git a/test/lib/macro-manager.js b/test/lib/macro-manager.js index 4dbed7c..5af5722 100644 --- a/test/lib/macro-manager.js +++ b/test/lib/macro-manager.js @@ -21,32 +21,6 @@ var CONFIG_DELAYED_HELLO_WORLD = [{ ], }]; -var CONFIG_HELLO_STATES = [{ - name: 'hello!', - provides: 'hello', - sequence: [ - ['say', 'hello'], - ], -}, { - name: 'world!', - requires: 'hello', - sequence: [ - ['say', 'world'], - ], -}, { - name: 'goodbye!', - resets: 'hello', - sequence: [ - ['say', 'goodbye'], - ], -}, { - name: 'something', - resets: 'world', - sequence: [ - ['say', 'goodbye'], - ], -}]; - var CONFIG_HELLO = [ { name: 'hello', @@ -350,6 +324,32 @@ describe('macros', function () { }); describe('state', function () { + var CONFIG_STATES = [{ + name: 'hello!', + provides: 'hello', + sequence: [ + ['say', 'hello'], + ], + }, { + name: 'world!', + requires: 'hello', + sequence: [ + ['say', 'world'], + ], + }, { + name: 'goodbye!', + resets: 'hello', + sequence: [ + ['say', 'goodbye'], + ], + }, { + name: 'something', + resets: 'world', + sequence: [ + ['say', 'goodbye'], + ], + }]; + describe('set, reset and query', function () { beforeEach(function () { resetMockAndFixture(); @@ -361,27 +361,27 @@ describe('macros', function () { }); it('should deliver current states as key value map', function () { - macros.init(CONFIG_HELLO_STATES); + macros.init(CONFIG_STATES); assert.deepEqual(macros.getCurrentStates(), { hello: 'clear', world: 'clear' }); }); it('should set state after execution', function () { - macros.init(CONFIG_HELLO_STATES); + macros.init(CONFIG_STATES); assert.deepEqual(macros.getCurrentStates(), { hello: 'clear', world: 'clear' }, 'not called'); - macros.execute(CONFIG_HELLO_STATES[0].name); + macros.execute(CONFIG_STATES[0].name); clock.tick(51); assert.deepEqual(macros.getCurrentStates(), { hello: 'set', world: 'clear' }, 'macro called'); }); it('should reset state after execution', function () { - macros.init(CONFIG_HELLO_STATES); - macros.execute(CONFIG_HELLO_STATES[0].name); + macros.init(CONFIG_STATES); + macros.execute(CONFIG_STATES[0].name); clock.tick(51); assert.deepEqual(macros.getCurrentStates(), { hello: 'set', world: 'clear' }, 'state set called'); - macros.execute(CONFIG_HELLO_STATES[2].name); + macros.execute(CONFIG_STATES[2].name); clock.tick(51); assert.deepEqual(macros.getCurrentStates(), { hello: 'clear', world: 'clear' }, 'state reset called'); @@ -396,7 +396,31 @@ describe('macros', function () { }); }); + // TODO validation of providers, requesters, resetters and deniers: + // providers and deniers need match! + describe('dependency call', function () { + var CONFIG_MULTI_STATES = [{ + name: 'Lamp', + provides: 'light', + sequence: [ + ['delay', '50'], + ['say', 'shiny'], + ], + }, { + name: 'he', + requires: 'light', + sequence: [ + ['say', 'uh'], + ], + }, { + name: 'she', + requires: 'light', + sequence: [ + ['say', 'ah'], + ], + }]; + beforeEach(function () { resetMockAndFixture(); clock = sinon.useFakeTimers(); @@ -406,15 +430,57 @@ describe('macros', function () { clock.restore(); }); - it.skip('should call macro if state is requested', function () { - macros.init(CONFIG_HELLO_STATES); - macros.execute(CONFIG_HELLO_STATES[1].name); + it('should call macro if state is requested', function () { + macros.init(CONFIG_STATES); + macros.execute(CONFIG_STATES[1].name); clock.tick(51); assert.deepEqual(deviceMock.operations, [['say', 'hello'], ['say', 'world']], 'calls are not done as expected'); - assert.deepEqual(macros.getCurrentStates(), { hello: true, world: false }, + assert.deepEqual(macros.getCurrentStates(), { hello: 'set', world: 'clear' }, 'states are not set as required'); }); + + it('should call a macro only once, if two triggered macros request it simultaneously', + function () { + macros.init(CONFIG_MULTI_STATES); + macros.execute(CONFIG_MULTI_STATES[1].name); + clock.tick(20); + macros.execute(CONFIG_MULTI_STATES[2].name); + assert.deepEqual(macros.getCurrentStates(), { light: 'going_to_be_set' }, + 'states are not in progress as they should'); + clock.tick(131); + assert.deepEqual(deviceMock.operations, + [['say', 'shiny'], ['say', 'uh'], ['say', 'ah']], 'calls are not done as expected'); + assert.deepEqual(macros.getCurrentStates(), { light: 'set' }, + 'states are not set as required'); + } + ); + + it('should call a macro only if the required state is not already set.', + function () { + macros.init(CONFIG_MULTI_STATES); + macros.execute(CONFIG_MULTI_STATES[1].name); + clock.tick(101); + assert.deepEqual(deviceMock.operations, + [['say', 'shiny'], ['say', 'uh']], 'first calls are not done as expected'); + assert.deepEqual(macros.getCurrentStates(), { light: 'set' }, + 'first states are not set as required'); + macros.execute(CONFIG_MULTI_STATES[2].name); + clock.tick(51); + assert.deepEqual(deviceMock.operations, + [['say', 'shiny'], ['say', 'uh'], ['say', 'ah']], + 'final calls are not done as expected'); + assert.deepEqual(macros.getCurrentStates(), { light: 'set' }, + 'final states are not set as required'); + } + ); + + it.skip('should call multiple macros if multiple states are requested', function () { + macros.init(CONFIG_MULTI_STATES); + macros.execute(CONFIG_MULTI_STATES[1].name); + clock.tick(51); + // TODO + }); }); }); }); From 64381f80411877a5e66f60b685f97b7ff550e8a6 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Thu, 4 Feb 2016 07:51:29 +0100 Subject: [PATCH 13/27] refactoring: introduce state initialization to streamline tests --- lib/macro-constants.js | 7 ++++ lib/macro-manager.js | 34 ++++++++++++--- test/lib/macro-manager.js | 88 ++++++++++++++++++++++++++------------- 3 files changed, 93 insertions(+), 36 deletions(-) diff --git a/lib/macro-constants.js b/lib/macro-constants.js index 3d7efab..84b266e 100644 --- a/lib/macro-constants.js +++ b/lib/macro-constants.js @@ -3,4 +3,11 @@ module.exports = { STATE_SET: 'set', STATE_NOT_SET: 'clear', STATE_PENDING_SET: 'going_to_be_set', + STATE_PENDING_RESET: 'going_to_be_clear', + isStateDefined: function (value) { + return value === this.STATE_NOT_SET + || value === this.STATE_SET + || value === this.STATE_PENDING_SET + || value === this.STATE_PENDING_RESET; + }, }; diff --git a/lib/macro-manager.js b/lib/macro-manager.js index 5a93d96..728fe24 100644 --- a/lib/macro-manager.js +++ b/lib/macro-manager.js @@ -66,13 +66,21 @@ function StateManager() { state.setProvidingMacro(macro); }; - this.setStateByName = function (name, stateValue) { - var state = this.stateByName[name]; - state.value = stateValue; + this.setStateByName = function (stateName, stateValue) { + var state = this.getStateByName(stateName); + if (constants.isStateDefined(stateValue)) { + state.value = stateValue; + } else { + throw new Error('State is not defined: ' + stateValue); + } }; - this.getStateByName = function (name) { - return this.stateByName[name]; + this.getStateByName = function (stateName) { + var state = this.stateByName[stateName]; + if (!state) { + throw new Error('Unknown state name: ' + stateName); + } + return state; }; this.getStateSummary = function () { @@ -83,6 +91,17 @@ function StateManager() { } return summary; }; + + this.initStates = function (summary) { + var stateValue; + var stateName; + for (stateName in summary) { + if (summary.hasOwnProperty(stateName)) { + stateValue = summary[stateName]; + this.setStateByName(stateName, stateValue); + } + } + }; } function registerMacro(macroConfig) { @@ -95,10 +114,13 @@ function registerMacro(macroConfig) { } } -function init(newConfiguration) { +function init(newConfiguration, initialStates) { newConfiguration.forEach(function (macro) { registerMacro(macro); }); + if (initialStates) { + virtualStates.initStates(initialStates); + } } function resetConfiguration() { diff --git a/test/lib/macro-manager.js b/test/lib/macro-manager.js index 5af5722..4bcb00c 100644 --- a/test/lib/macro-manager.js +++ b/test/lib/macro-manager.js @@ -321,6 +321,11 @@ describe('macros', function () { assert.equal(macros.isMacroDefined('hello world'), true); assert.equal(macros.isMacroDefined('Slartibartfass'), false); }); + + // TODO validation of providers, requesters, reseters: + // if a requester exist, provider and resetter need to match! + // if states are used at initialization, these have to be defined in config and have a valid + // value, else a error message has to be provided. }); describe('state', function () { @@ -366,9 +371,7 @@ describe('macros', function () { }); it('should set state after execution', function () { - macros.init(CONFIG_STATES); - assert.deepEqual(macros.getCurrentStates(), - { hello: 'clear', world: 'clear' }, 'not called'); + macros.init(CONFIG_STATES, { hello: 'clear', world: 'clear' }); macros.execute(CONFIG_STATES[0].name); clock.tick(51); assert.deepEqual(macros.getCurrentStates(), @@ -376,11 +379,7 @@ describe('macros', function () { }); it('should reset state after execution', function () { - macros.init(CONFIG_STATES); - macros.execute(CONFIG_STATES[0].name); - clock.tick(51); - assert.deepEqual(macros.getCurrentStates(), { hello: 'set', world: 'clear' }, - 'state set called'); + macros.init(CONFIG_STATES, { hello: 'set', world: 'clear' }); macros.execute(CONFIG_STATES[2].name); clock.tick(51); assert.deepEqual(macros.getCurrentStates(), { hello: 'clear', world: 'clear' }, @@ -396,9 +395,6 @@ describe('macros', function () { }); }); - // TODO validation of providers, requesters, resetters and deniers: - // providers and deniers need match! - describe('dependency call', function () { var CONFIG_MULTI_STATES = [{ name: 'Lamp', @@ -433,7 +429,6 @@ describe('macros', function () { it('should call macro if state is requested', function () { macros.init(CONFIG_STATES); macros.execute(CONFIG_STATES[1].name); - clock.tick(51); assert.deepEqual(deviceMock.operations, [['say', 'hello'], ['say', 'world']], 'calls are not done as expected'); assert.deepEqual(macros.getCurrentStates(), { hello: 'set', world: 'clear' }, @@ -456,30 +451,63 @@ describe('macros', function () { } ); - it('should call a macro only if the required state is not already set.', + it('should call a macro only if the requested state is not already set.', function () { - macros.init(CONFIG_MULTI_STATES); - macros.execute(CONFIG_MULTI_STATES[1].name); - clock.tick(101); - assert.deepEqual(deviceMock.operations, - [['say', 'shiny'], ['say', 'uh']], 'first calls are not done as expected'); - assert.deepEqual(macros.getCurrentStates(), { light: 'set' }, - 'first states are not set as required'); + macros.init(CONFIG_MULTI_STATES, { light: 'set' }); macros.execute(CONFIG_MULTI_STATES[2].name); - clock.tick(51); - assert.deepEqual(deviceMock.operations, - [['say', 'shiny'], ['say', 'uh'], ['say', 'ah']], - 'final calls are not done as expected'); + assert.deepEqual(deviceMock.operations, [['say', 'ah']], + 'calls are not done as expected'); assert.deepEqual(macros.getCurrentStates(), { light: 'set' }, - 'final states are not set as required'); + 'states are not set as required'); } ); + }); - it.skip('should call multiple macros if multiple states are requested', function () { - macros.init(CONFIG_MULTI_STATES); - macros.execute(CONFIG_MULTI_STATES[1].name); - clock.tick(51); - // TODO + describe('dependency call', function () { + var CONFIG_MULTI_STATES = [{ + name: 'Screen down', + provides: 'screen', + sequence: [ + ['say', 'screen down'], + ], + }, { + name: 'Projector on', + requires: 'screen', + provides: 'projector', + sequence: [ + ['say', 'projector on'], + ], + }, { + name: 'Screen up', + resets: 'screen', + sequence: [ + ['say', 'screen up'], + ], + }, { + name: 'Projector off', + resets: 'projector', + sequence: [ + ['say', 'projector off'], + ], + }]; + + beforeEach(function () { + resetMockAndFixture(); + clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + }); + + it.skip('should call a state X resetting macro, if X requires Y and this one resets Y', + function () { + macros.init(CONFIG_MULTI_STATES, { screen: 'set', projector: 'set' }); + macros.execute(CONFIG_MULTI_STATES[2].name); + assert.deepEqual(deviceMock.operations, [['say', 'projector off'], ['say', 'screen up']], + 'calls are not done as expected'); + assert.deepEqual(macros.getCurrentStates(), { screen: 'clear', projector: 'clear' }, + 'states are not set as required'); }); }); }); From 58b8d6ac0680300469baaf0f0277b7faedf8e8f2 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Tue, 9 Feb 2016 22:38:46 +0100 Subject: [PATCH 14/27] implemented recursive switching off depending on virtual states --- lib/macro-manager.js | 107 ++++++++++++++++++++++++++++++++++---- lib/macro.js | 10 +++- test/lib/macro-manager.js | 2 +- 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/lib/macro-manager.js b/lib/macro-manager.js index 728fe24..02bbf08 100644 --- a/lib/macro-manager.js +++ b/lib/macro-manager.js @@ -9,27 +9,53 @@ var visibleMacroNames = []; var virtualStates; var getMacroClosureByName; +function Subject() { + var instance = this; + this.subscribers = []; + this.registerSubscriber = function (subscriber) { + instance.subscribers.push(subscriber); + }; + + this.notifyAll = function () { + var callback = null; + while (instance.subscribers.length > 0) { + callback = instance.subscribers.shift(); + callback(); + } + }; +} + function State(name, value) { var stateInstance = this; this.name = name; this.value = value; this.providingMacro = null; - this.toBeCalledOnRisingEdge = []; + this.resettingMacro = null; + this.dependantMacros = []; + this.toBeCalledOnRisingEdge = new Subject(); this.setProvidingMacro = function (macro) { this.providingMacro = macro; }; this.registerWaitingMacro = function (waitingDoneCallback) { - this.toBeCalledOnRisingEdge.push(waitingDoneCallback); + this.toBeCalledOnRisingEdge.registerSubscriber(waitingDoneCallback); }; this.notifyWaitingMacros = function () { - var callback; - while (this.toBeCalledOnRisingEdge.length > 0) { - callback = this.toBeCalledOnRisingEdge.shift(); - callback(); - } + this.toBeCalledOnRisingEdge.notifyAll(); + }; + + this.addDependantMacro = function (macro) { + this.dependantMacros.push(macro); + }; + + this.setResettingMacro = function (macro) { + this.resettingMacro = macro; + }; + + this.callResettingMacro = function (callback) { + this.resettingMacro.execute(callback); }; this.ensureIsSet = function (done) { @@ -47,6 +73,21 @@ function State(name, value) { done(); } }; + + this.getDependantStateNames = function () { + var i; + var dependantMacro; + var stateNames = []; + var dependantStateName; + for (i = 0; i < this.dependantMacros.length; i++) { + dependantMacro = this.dependantMacros[i]; + dependantStateName = dependantMacro.getProvidedStateName(); + if (dependantStateName) { + stateNames.push(dependantStateName); + } + } + return stateNames; + }; } function StateManager() { @@ -66,6 +107,16 @@ function StateManager() { state.setProvidingMacro(macro); }; + this.registerStateRequester = function (stateName, macro) { + var state = this.registerState(stateName); + state.addDependantMacro(macro); + }; + + this.registerStateResetter = function (stateName, macro) { + var state = this.registerState(stateName); + state.setResettingMacro(macro); + }; + this.setStateByName = function (stateName, stateValue) { var state = this.getStateByName(stateName); if (constants.isStateDefined(stateValue)) { @@ -87,7 +138,9 @@ function StateManager() { var summary = {}; var stateName; for (stateName in this.stateByName) { - summary[stateName] = this.stateByName[stateName].value; + if (this.stateByName.hasOwnProperty(stateName)) { + summary[stateName] = this.stateByName[stateName].value; + } } return summary; }; @@ -161,19 +214,49 @@ function getCurrentStates() { return virtualStates.getStateSummary(); } -function registerState(stateName) { - virtualStates.registerState(stateName); +function registerStateResetter(stateName, macro) { + virtualStates.registerStateResetter(stateName, macro); } function registerStateProvider(stateName, macro) { virtualStates.registerStateProvider(stateName, macro); } +function registerStateRequester(stateName, macro) { + virtualStates.registerStateRequester(stateName, macro); +} + function requestStateIsSet(callback, stateName) { var state = virtualStates.getStateByName(stateName); state.ensureIsSet(callback); } +function resetStatesByName(stateNames, callback) { + var callbacksToCollect = stateNames.length; + var collector = function () { + if (--callbacksToCollect === 0) { + callback(); + } + }; + + var i; + var state; + for (i = 0; i < stateNames.length; i++) { + state = virtualStates.getStateByName(stateNames[i]); + state.callResettingMacro(collector); + } +} + +function requestClearStateIsSave(callback, stateName) { + var stateToClearReset = virtualStates.getStateByName(stateName); + var stateNames = stateToClearReset.getDependantStateNames(); + if (stateNames.length > 0) { + resetStatesByName(stateNames, callback); + } else { + callback(); + } +} + function setStateByName(stateName, newState) { virtualStates.setStateByName(stateName, newState); } @@ -188,8 +271,10 @@ function getDeviceByName(deviceName) { manager = { registerStateProvider: registerStateProvider, - registerState: registerState, + registerStateRequester: registerStateRequester, + registerStateResetter: registerStateResetter, requestStateIsSet: requestStateIsSet, + requestClearStateIsSave: requestClearStateIsSave, setStateByName: setStateByName, getCatchAllDevice: getCatchAllDevice, getDeviceByName: getDeviceByName, diff --git a/lib/macro.js b/lib/macro.js index 6d7f6e0..d6b370b 100644 --- a/lib/macro.js +++ b/lib/macro.js @@ -128,10 +128,10 @@ function Macro(config, macroManager) { macroManager.registerStateProvider(config.provides, this); } if (this.isRequiringState()) { - macroManager.registerState(config.requires); + macroManager.registerStateRequester(config.requires, this); } if (this.isResettingState()) { - macroManager.registerState(config.resets); + macroManager.registerStateResetter(config.resets, this); } }; @@ -139,11 +139,17 @@ function Macro(config, macroManager) { this.callersCallback = done; if (this.isRequiringState()) { macroManager.requestStateIsSet(this.macroClosureChain, config.requires); + } else if (this.isResettingState()) { + macroManager.requestClearStateIsSave(this.macroClosureChain, config.resets); } else { this.macroClosureChain(); } }; + this.getProvidedStateName = function () { + return config.provides; + }; + this.macroClosureChain = this.createClosureChain(); this.registerStates(); } diff --git a/test/lib/macro-manager.js b/test/lib/macro-manager.js index 4bcb00c..62be8ef 100644 --- a/test/lib/macro-manager.js +++ b/test/lib/macro-manager.js @@ -500,7 +500,7 @@ describe('macros', function () { clock.restore(); }); - it.skip('should call a state X resetting macro, if X requires Y and this one resets Y', + it('should call a state X resetting macro, if X requires Y and this one resets Y', function () { macros.init(CONFIG_MULTI_STATES, { screen: 'set', projector: 'set' }); macros.execute(CONFIG_MULTI_STATES[2].name); From d3599be384a426e05c5c2022713c5bcb4bc4d33f Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Mon, 22 Feb 2016 19:58:48 +0100 Subject: [PATCH 15/27] change config name to osur --- app.js | 5 +++-- lib/macro-manager.js | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 950ae1a..2a39fea 100644 --- a/app.js +++ b/app.js @@ -21,7 +21,7 @@ var JST = { // Create app var app = module.exports = express(); -// lirc_web configuration +// osur configuration var config = {}; var hasServerPortConfig = false; var hasSSLConfig = false; @@ -57,7 +57,8 @@ function readConfiguration() { try { configure(__dirname + '/config.json'); } catch (e) { - configure(process.env.HOME + '/.lirc_web_config.json'); + console.log (e); + configure(process.env.HOME + '/.osur-config.json'); } } catch (e) { console.log('DEBUG:', e); diff --git a/lib/macro-manager.js b/lib/macro-manager.js index 02bbf08..d899d10 100644 --- a/lib/macro-manager.js +++ b/lib/macro-manager.js @@ -6,7 +6,7 @@ var macroConfigurations = {}; var macros = {}; var manager = {}; var visibleMacroNames = []; -var virtualStates; +var virtualStates = new StateManager(); var getMacroClosureByName; function Subject() { @@ -159,6 +159,7 @@ function StateManager() { function registerMacro(macroConfig) { var macroName = macroConfig.name; + console.log("registerMacro: " + JSON.stringify(macroConfig)); var macro = new Macro(macroConfig, manager); macroConfigurations[macroName] = macroConfig; macros[macroName] = macro; From 9bc4aabb262ca7bb42b151f4fb0e1e3aff196e7d Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sat, 27 Feb 2016 12:07:36 +0100 Subject: [PATCH 16/27] cleanup use of later defined function --- lib/macro-manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/macro-manager.js b/lib/macro-manager.js index d899d10..22cfe5c 100644 --- a/lib/macro-manager.js +++ b/lib/macro-manager.js @@ -6,7 +6,7 @@ var macroConfigurations = {}; var macros = {}; var manager = {}; var visibleMacroNames = []; -var virtualStates = new StateManager(); +var virtualStates; var getMacroClosureByName; function Subject() { @@ -156,10 +156,10 @@ function StateManager() { } }; } +virtualStates = new StateManager(); function registerMacro(macroConfig) { var macroName = macroConfig.name; - console.log("registerMacro: " + JSON.stringify(macroConfig)); var macro = new Macro(macroConfig, manager); macroConfigurations[macroName] = macroConfig; macros[macroName] = macro; From e5aa78573d471cf79876efb6066e3fc8a45e2630 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sat, 27 Feb 2016 12:13:15 +0100 Subject: [PATCH 17/27] help user detect malicious configuration and fix spaces --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 2a39fea..ed35cf2 100644 --- a/app.js +++ b/app.js @@ -57,7 +57,7 @@ function readConfiguration() { try { configure(__dirname + '/config.json'); } catch (e) { - console.log (e); + console.log('DEBUG:', e); configure(process.env.HOME + '/.osur-config.json'); } } catch (e) { From 450114719403742206d045fc8b85a89594f3e0e2 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sat, 27 Feb 2016 19:25:28 +0100 Subject: [PATCH 18/27] preserve state on reload --- app.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index ed35cf2..e5bfbd6 100644 --- a/app.js +++ b/app.js @@ -89,16 +89,20 @@ function overrideConfigurationForDebugOrDevelopment() { function initializeModules(done) { lirc.init(config, function () { + var currentStates = null; + if (config.gpios) { gpio.init(config.gpios); } if (config.macros) { + currentStates = macros.getCurrentStates(); + if (config.gpios) { macros.registerDevice(gpio); } macros.registerDevice(lirc); - macros.init(config.macros); + macros.init(config.macros, currentStates); } // initialize Labels for remotes / commands From 0d740e0a62a2784c1fdddc88be1105cdcabf0ac1 Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sat, 27 Feb 2016 19:32:40 +0100 Subject: [PATCH 19/27] don't add macros twice on reload --- app.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app.js b/app.js index e5bfbd6..a6bb4a5 100644 --- a/app.js +++ b/app.js @@ -88,21 +88,24 @@ function overrideConfigurationForDebugOrDevelopment() { } function initializeModules(done) { - lirc.init(config, function () { - var currentStates = null; + function initializeMacros() { + var currentStates = macros.getCurrentStates(); + macros.resetConfiguration(); + if (config.gpios) { + macros.registerDevice(gpio); + } + macros.registerDevice(lirc); + macros.init(config.macros, currentStates); + } + + lirc.init(config, function () { if (config.gpios) { gpio.init(config.gpios); } if (config.macros) { - currentStates = macros.getCurrentStates(); - - if (config.gpios) { - macros.registerDevice(gpio); - } - macros.registerDevice(lirc); - macros.init(config.macros, currentStates); + initializeMacros(); } // initialize Labels for remotes / commands From 8b07b12f937ebcf3bc7dae1376b2a545e611c28f Mon Sep 17 00:00:00 2001 From: Ovis Maximus Date: Sat, 27 Feb 2016 20:38:35 +0100 Subject: [PATCH 20/27] don't call reset macros if not necessary --- lib/macro-manager.js | 6 +++++- test/lib/macro-manager.js | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/macro-manager.js b/lib/macro-manager.js index 22cfe5c..6bcb6b1 100644 --- a/lib/macro-manager.js +++ b/lib/macro-manager.js @@ -55,7 +55,11 @@ function State(name, value) { }; this.callResettingMacro = function (callback) { - this.resettingMacro.execute(callback); + if (this.value === constants.STATE_SET) { + this.resettingMacro.execute(callback); + } else { + callback(); + } }; this.ensureIsSet = function (done) { diff --git a/test/lib/macro-manager.js b/test/lib/macro-manager.js index 62be8ef..3a056de 100644 --- a/test/lib/macro-manager.js +++ b/test/lib/macro-manager.js @@ -463,7 +463,7 @@ describe('macros', function () { ); }); - describe('dependency call', function () { + describe('dependency switch off', function () { var CONFIG_MULTI_STATES = [{ name: 'Screen down', provides: 'screen', @@ -509,6 +509,17 @@ describe('macros', function () { assert.deepEqual(macros.getCurrentStates(), { screen: 'clear', projector: 'clear' }, 'states are not set as required'); }); + + it('should call a state X resetting macro only, if X requires Y, ' + + 'X is set and this one resets Y', + function () { + macros.init(CONFIG_MULTI_STATES, { screen: 'set', projector: 'clear' }); + macros.execute(CONFIG_MULTI_STATES[2].name); + assert.deepEqual(deviceMock.operations, [['say', 'screen up']], + 'calls are not done as expected'); + assert.deepEqual(macros.getCurrentStates(), { screen: 'clear', projector: 'clear' }, + 'states are not set as required'); + }); }); }); }); From 99920b6c7e562be4f3948a92f5c6ada44a6cc34c Mon Sep 17 00:00:00 2001 From: OvisMaximus Date: Mon, 1 Jan 2018 16:05:39 +0100 Subject: [PATCH 21/27] remove noise from test output --- app.js | 1 - static/css/compiled/app.css | 6 +++--- static/js/compiled/app.js | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index a6bb4a5..c39f537 100644 --- a/app.js +++ b/app.js @@ -75,7 +75,6 @@ function readConfiguration() { function overrideConfigurationForDebugOrDevelopment() { var lircTest; if (process.env.npm_package_config_test_env) { - console.log('we are in test mode!'); lircTest = require('./test/lib/lirc'); lircTest.replaceLircByMock(); gpio.setGpioLibrary(require('./lib/gpio-la-mock')); diff --git a/static/css/compiled/app.css b/static/css/compiled/app.css index b70265f..1b09a8e 100644 --- a/static/css/compiled/app.css +++ b/static/css/compiled/app.css @@ -21,7 +21,7 @@ ul { padding: 0; } h1 { - background: #34495e; + background: #34495E; color: #fff; font-family: LatoBold; margin: 0 0 30px; @@ -34,13 +34,13 @@ h1 { width: 100%; } h1.is-remote { - background: #3498db; + background: #3498DB; } .command { margin: 20px 0; } .command:nth-child(even) .btn-primary { - background: #16a085; + background: #16A085; } .no-touch .command-link:hover { background: #2fe2bf; diff --git a/static/js/compiled/app.js b/static/js/compiled/app.js index 81c732c..209f767 100644 --- a/static/js/compiled/app.js +++ b/static/js/compiled/app.js @@ -1,2 +1,2 @@ -function FastClick(a){"use strict";var b,c=this;if(this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.layer=a,!a||!a.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){return FastClick.prototype.onClick.apply(c,arguments)},this.onMouse=function(){return FastClick.prototype.onMouse.apply(c,arguments)},this.onTouchStart=function(){return FastClick.prototype.onTouchStart.apply(c,arguments)},this.onTouchEnd=function(){return FastClick.prototype.onTouchEnd.apply(c,arguments)},this.onTouchCancel=function(){return FastClick.prototype.onTouchCancel.apply(c,arguments)},"undefined"!=typeof window.ontouchstart&&(this.deviceIsAndroid&&(a.addEventListener("mouseover",this.onMouse,!0),a.addEventListener("mousedown",this.onMouse,!0),a.addEventListener("mouseup",this.onMouse,!0)),a.addEventListener("click",this.onClick,!0),a.addEventListener("touchstart",this.onTouchStart,!1),a.addEventListener("touchend",this.onTouchEnd,!1),a.addEventListener("touchcancel",this.onTouchCancel,!1),Event.prototype.stopImmediatePropagation||(a.removeEventListener=function(b,c,d){var e=Node.prototype.removeEventListener;"click"===b?e.call(a,b,c.hijacked||c,d):e.call(a,b,c,d)},a.addEventListener=function(b,c,d){var e=Node.prototype.addEventListener;"click"===b?e.call(a,b,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),d):e.call(a,b,c,d)}),"function"==typeof a.onclick&&(b=a.onclick,a.addEventListener("click",function(a){b(a)},!1),a.onclick=null))}!function(a){String.prototype.trim===a&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.reduce===a&&(Array.prototype.reduce=function(b){if(void 0===this||null===this)throw new TypeError;var c,d=Object(this),e=d.length>>>0,f=0;if("function"!=typeof b)throw new TypeError;if(0==e&&1==arguments.length)throw new TypeError;if(arguments.length>=2)c=arguments[1];else for(;;){if(f in d){c=d[f++];break}if(++f>=e)throw new TypeError}for(;e>f;)f in d&&(c=b.call(a,c,d[f],f,d)),f++;return c})}();var Zepto=function(){function a(a){return null==a?String(a):W[X.call(a)]||"object"}function b(b){return"function"==a(b)}function c(a){return null!=a&&a==a.window}function d(a){return null!=a&&a.nodeType==a.DOCUMENT_NODE}function e(b){return"object"==a(b)}function f(a){return e(a)&&!c(a)&&a.__proto__==Object.prototype}function g(a){return a instanceof Array}function h(a){return"number"==typeof a.length}function i(a){return E.call(a,function(a){return null!=a})}function j(a){return a.length>0?y.fn.concat.apply([],a):a}function k(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(a){return a in H?H[a]:H[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function m(a,b){return"number"!=typeof b||J[k(a)]?b:b+"px"}function n(a){var b,c;return G[a]||(b=F.createElement(a),F.body.appendChild(b),c=I(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),"none"==c&&(c="block"),G[a]=c),G[a]}function o(a){return"children"in a?D.call(a.children):y.map(a.childNodes,function(a){return 1==a.nodeType?a:void 0})}function p(a,b,c){for(x in b)c&&(f(b[x])||g(b[x]))?(f(b[x])&&!f(a[x])&&(a[x]={}),g(b[x])&&!g(a[x])&&(a[x]=[]),p(a[x],b[x],c)):b[x]!==w&&(a[x]=b[x])}function q(a,b){return b===w?y(a):y(a).filter(b)}function r(a,c,d,e){return b(c)?c.call(a,d,e):c}function s(a,b,c){null==c?a.removeAttribute(b):a.setAttribute(b,c)}function t(a,b){var c=a.className,d=c&&c.baseVal!==w;return b===w?d?c.baseVal:c:void(d?c.baseVal=b:a.className=b)}function u(a){var b;try{return a?"true"==a||("false"==a?!1:"null"==a?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?y.parseJSON(a):a:b):a}catch(c){return a}}function v(a,b){b(a);for(var c in a.childNodes)v(a.childNodes[c],b)}var w,x,y,z,A,B,C=[],D=C.slice,E=C.filter,F=window.document,G={},H={},I=F.defaultView.getComputedStyle,J={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},K=/^\s*<(\w+|!)[^>]*>/,L=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,M=/^(?:body|html)$/i,N=["val","css","html","text","data","width","height","offset"],O=["after","prepend","before","append"],P=F.createElement("table"),Q=F.createElement("tr"),R={tr:F.createElement("tbody"),tbody:P,thead:P,tfoot:P,td:Q,th:Q,"*":F.createElement("div")},S=/complete|loaded|interactive/,T=/^\.([\w-]+)$/,U=/^#([\w-]*)$/,V=/^[\w-]+$/,W={},X=W.toString,Y={},Z=F.createElement("div");return Y.matches=function(a,b){if(!a||1!==a.nodeType)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=Z).appendChild(a),d=~Y.qsa(e,b).indexOf(a),f&&Z.removeChild(a),d},A=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},B=function(a){return E.call(a,function(b,c){return a.indexOf(b)==c})},Y.fragment=function(a,b,c){a.replace&&(a=a.replace(L,"<$1>")),b===w&&(b=K.test(a)&&RegExp.$1),b in R||(b="*");var d,e,g=R[b];return g.innerHTML=""+a,e=y.each(D.call(g.childNodes),function(){g.removeChild(this)}),f(c)&&(d=y(e),y.each(c,function(a,b){N.indexOf(a)>-1?d[a](b):d.attr(a,b)})),e},Y.Z=function(a,b){return a=a||[],a.__proto__=y.fn,a.selector=b||"",a},Y.isZ=function(a){return a instanceof Y.Z},Y.init=function(a,c){if(!a)return Y.Z();if(b(a))return y(F).ready(a);if(Y.isZ(a))return a;var d;if(g(a))d=i(a);else if(e(a))d=[f(a)?y.extend({},a):a],a=null;else if(K.test(a))d=Y.fragment(a.trim(),RegExp.$1,c),a=null;else{if(c!==w)return y(c).find(a);d=Y.qsa(F,a)}return Y.Z(d,a)},y=function(a,b){return Y.init(a,b)},y.extend=function(a){var b,c=D.call(arguments,1);return"boolean"==typeof a&&(b=a,a=c.shift()),c.forEach(function(c){p(a,c,b)}),a},Y.qsa=function(a,b){var c;return d(a)&&U.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:1!==a.nodeType&&9!==a.nodeType?[]:D.call(T.test(b)?a.getElementsByClassName(RegExp.$1):V.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},y.contains=function(a,b){return a!==b&&a.contains(b)},y.type=a,y.isFunction=b,y.isWindow=c,y.isArray=g,y.isPlainObject=f,y.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},y.inArray=function(a,b,c){return C.indexOf.call(b,a,c)},y.camelCase=A,y.trim=function(a){return a.trim()},y.uuid=0,y.support={},y.expr={},y.map=function(a,b){var c,d,e,f=[];if(h(a))for(d=0;d=0?a:a+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(a){return C.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return b(a)?this.not(this.not(a)):y(E.call(this,function(b){return Y.matches(b,a)}))},add:function(a,b){return y(B(this.concat(y(a,b))))},is:function(a){return this.length>0&&Y.matches(this[0],a)},not:function(a){var c=[];if(b(a)&&a.call!==w)this.each(function(b){a.call(this,b)||c.push(this)});else{var d="string"==typeof a?this.filter(a):h(a)&&b(a.item)?D.call(a):y(a);this.forEach(function(a){d.indexOf(a)<0&&c.push(a)})}return y(c)},has:function(a){return this.filter(function(){return e(a)?y.contains(this,a):y(this).find(a).size()})},eq:function(a){return-1===a?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!e(a)?a:y(a)},last:function(){var a=this[this.length-1];return a&&!e(a)?a:y(a)},find:function(a){var b,c=this;return b="object"==typeof a?y(a).filter(function(){var a=this;return C.some.call(c,function(b){return y.contains(b,a)})}):1==this.length?y(Y.qsa(this[0],a)):this.map(function(){return Y.qsa(this,a)})},closest:function(a,b){var c=this[0],e=!1;for("object"==typeof a&&(e=y(a));c&&!(e?e.indexOf(c)>=0:Y.matches(c,a));)c=c!==b&&!d(c)&&c.parentNode;return y(c)},parents:function(a){for(var b=[],c=this;c.length>0;)c=y.map(c,function(a){return(a=a.parentNode)&&!d(a)&&b.indexOf(a)<0?(b.push(a),a):void 0});return q(b,a)},parent:function(a){return q(B(this.pluck("parentNode")),a)},children:function(a){return q(this.map(function(){return o(this)}),a)},contents:function(){return this.map(function(){return D.call(this.childNodes)})},siblings:function(a){return q(this.map(function(a,b){return E.call(o(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return y.map(this,function(b){return b[a]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=null),"none"==I(this,"").getPropertyValue("display")&&(this.style.display=n(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var c=b(a);if(this[0]&&!c)var d=y(a).get(0),e=d.parentNode||this.length>1;return this.each(function(b){y(this).wrapAll(c?a.call(this,b):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){y(this[0]).before(a=y(a));for(var b;(b=a.children()).length;)a=b.first();y(a).append(this)}return this},wrapInner:function(a){var c=b(a);return this.each(function(b){var d=y(this),e=d.contents(),f=c?a.call(this,b):a;e.length?e.wrapAll(f):d.append(f)})},unwrap:function(){return this.parent().each(function(){y(this).replaceWith(y(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(a){return this.each(function(){var b=y(this);(a===w?"none"==b.css("display"):a)?b.show():b.hide()})},prev:function(a){return y(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return y(this.pluck("nextElementSibling")).filter(a||"*")},html:function(a){return a===w?this.length>0?this[0].innerHTML:null:this.each(function(b){var c=this.innerHTML;y(this).empty().append(r(this,a,b,c))})},text:function(a){return a===w?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=a})},attr:function(a,b){var c;return"string"==typeof a&&b===w?0==this.length||1!==this[0].nodeType?w:"value"==a&&"INPUT"==this[0].nodeName?this.val():!(c=this[0].getAttribute(a))&&a in this[0]?this[0][a]:c:this.each(function(c){if(1===this.nodeType)if(e(a))for(x in a)s(this,x,a[x]);else s(this,a,r(this,b,c,this.getAttribute(a)))})},removeAttr:function(a){return this.each(function(){1===this.nodeType&&s(this,a)})},prop:function(a,b){return b===w?this[0]&&this[0][a]:this.each(function(c){this[a]=r(this,b,c,this[a])})},data:function(a,b){var c=this.attr("data-"+k(a),b);return null!==c?u(c):w},val:function(a){return a===w?this[0]&&(this[0].multiple?y(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(b){this.value=r(this,a,b,this.value)})},offset:function(a){if(a)return this.each(function(b){var c=y(this),d=r(this,a,b,c.offset()),e=c.offsetParent().offset(),f={top:d.top-e.top,left:d.left-e.left};"static"==c.css("position")&&(f.position="relative"),c.css(f)});if(0==this.length)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(b,c){if(arguments.length<2&&"string"==typeof b)return this[0]&&(this[0].style[A(b)]||I(this[0],"").getPropertyValue(b));var d="";if("string"==a(b))c||0===c?d=k(b)+":"+m(b,c):this.each(function(){this.style.removeProperty(k(b))});else for(x in b)b[x]||0===b[x]?d+=k(x)+":"+m(x,b[x])+";":this.each(function(){this.style.removeProperty(k(x))});return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(y(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return C.some.call(this,function(a){return this.test(t(a))},l(a))},addClass:function(a){return this.each(function(b){z=[];var c=t(this),d=r(this,a,b,c);d.split(/\s+/g).forEach(function(a){y(this).hasClass(a)||z.push(a)},this),z.length&&t(this,c+(c?" ":"")+z.join(" "))})},removeClass:function(a){return this.each(function(b){return a===w?t(this,""):(z=t(this),r(this,a,b,z).split(/\s+/g).forEach(function(a){z=z.replace(l(a)," ")}),t(this,z.trim()),void 0)})},toggleClass:function(a,b){return this.each(function(c){var d=y(this),e=r(this,a,c,t(this));e.split(/\s+/g).forEach(function(a){(b===w?!d.hasClass(a):b)?d.addClass(a):d.removeClass(a)})})},scrollTop:function(){return this.length?"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY:void 0},position:function(){if(this.length){var a=this[0],b=this.offsetParent(),c=this.offset(),d=M.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(y(a).css("margin-top"))||0,c.left-=parseFloat(y(a).css("margin-left"))||0,d.top+=parseFloat(y(b[0]).css("border-top-width"))||0,d.left+=parseFloat(y(b[0]).css("border-left-width"))||0,{top:c.top-d.top,left:c.left-d.left}}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||F.body;a&&!M.test(a.nodeName)&&"static"==y(a).css("position");)a=a.offsetParent;return a})}},y.fn.detach=y.fn.remove,["width","height"].forEach(function(a){y.fn[a]=function(b){var e,f=this[0],g=a.replace(/./,function(a){return a[0].toUpperCase()});return b===w?c(f)?f["inner"+g]:d(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[a]:this.each(function(c){f=y(this),f.css(a,r(this,b,c,f[a]()))})}}),O.forEach(function(b,c){var d=c%2;y.fn[b]=function(){var b,e,f=y.map(arguments,function(c){return b=a(c),"object"==b||"array"==b||null==c?c:Y.fragment(c)}),g=this.length>1;return f.length<1?this:this.each(function(a,b){e=d?b:b.parentNode,b=0==c?b.nextSibling:1==c?b.firstChild:2==c?b:null,f.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!e)return y(a).remove();v(e.insertBefore(a,b),function(a){null!=a.nodeName&&"SCRIPT"===a.nodeName.toUpperCase()&&(!a.type||"text/javascript"===a.type)&&!a.src&&window.eval.call(window,a.innerHTML)})})})},y.fn[d?b+"To":"insert"+(c?"Before":"After")]=function(a){return y(a)[b](this),this}}),Y.Z.prototype=y.fn,Y.uniq=B,Y.deserializeValue=u,y.zepto=Y,y}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);(c.webkit=!!d)&&(c.version=d[1]),e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function b(a){return a._zid||(a._zid=n++)}function c(a,c,f,g){if(c=d(c),c.ns)var h=e(c.ns);return(m[b(a)]||[]).filter(function(a){return a&&(!c.e||a.e==c.e)&&(!c.ns||h.test(a.ns))&&(!f||b(a.fn)===b(f))&&(!g||a.sel==g)})}function d(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function e(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function f(b,c,d){"string"!=a.type(b)?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function g(a,b){return a.del&&("focus"==a.e||"blur"==a.e)||!!b}function h(a){return p[a]||a}function i(c,e,i,j,k,l){var n=b(c),o=m[n]||(m[n]=[]);f(e,i,function(b,e){var f=d(b);f.fn=e,f.sel=j,f.e in p&&(e=function(b){var c=b.relatedTarget;return!c||c!==this&&!a.contains(this,c)?f.fn.apply(this,arguments):void 0}),f.del=k&&k(e,b);var i=f.del||e;f.proxy=function(a){var b=i.apply(c,[a].concat(a.data));return b===!1&&(a.preventDefault(),a.stopPropagation()),b},f.i=o.length,o.push(f),c.addEventListener(h(f.e),f.proxy,g(f,l))})}function j(a,d,e,i,j){var k=b(a);f(d||"",e,function(b,d){c(a,b,d,i).forEach(function(b){delete m[k][b.i],a.removeEventListener(h(b.e),b.proxy,g(b,j))})})}function k(b){var c,d={originalEvent:b};for(c in b)!s.test(c)&&void 0!==b[c]&&(d[c]=b[c]);return a.each(t,function(a,c){d[a]=function(){return this[c]=q,b[a].apply(b,arguments)},d[c]=r}),d}function l(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var m=(a.zepto.qsa,{}),n=1,o={},p={mouseenter:"mouseover",mouseleave:"mouseout"};o.click=o.mousedown=o.mouseup=o.mousemove="MouseEvents",a.event={add:i,remove:j},a.proxy=function(c,d){if(a.isFunction(c)){var e=function(){return c.apply(d,arguments)};return e._zid=b(c),e}if("string"==typeof d)return a.proxy(c[d],c);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){i(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){j(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){i(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return j(d,b,a),c}})})};var q=function(){return!0},r=function(){return!1},s=/^([A-Z]|layer[XY]$)/,t={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){i(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);return g?(e=a.extend(k(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))):void 0}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){j(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){return("string"==typeof b||a.isPlainObject(b))&&(b=a.Event(b)),l(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,d){var e,f;return this.each(function(g,h){e=k("string"==typeof b?a.Event(b):b),e.data=d,e.target=h,a.each(c(h,b.type||b),function(a,b){return f=b.proxy(e),e.isImmediatePropagationStopped()?!1:void 0})}),f},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){"string"!=typeof a&&(b=a,a=b.type);var c=document.createEvent(o[a]||"Events"),d=!0;if(b)for(var e in b)"bubbles"==e?d=!!b[e]:c[e]=b[e];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function(a){function b(b,c,d){var e=a.Event(c);return a(b).trigger(e,d),!e.defaultPrevented}function c(a,c,d,e){return a.global?b(c||s,d,e):void 0}function d(b){b.global&&0===a.active++&&c(b,null,"ajaxStart")}function e(b){b.global&&!--a.active&&c(b,null,"ajaxStop")}function f(a,b){var d=b.context;return b.beforeSend.call(d,a,b)===!1||c(b,d,"ajaxBeforeSend",[a,b])===!1?!1:void c(b,d,"ajaxSend",[a,b])}function g(a,b,d){var e=d.context,f="success";d.success.call(e,a,f,b),c(d,e,"ajaxSuccess",[b,d,a]),i(f,b,d)}function h(a,b,d,e){var f=e.context;e.error.call(f,d,b,a),c(e,f,"ajaxError",[d,e,a]),i(b,d,e)}function i(a,b,d){var f=d.context;d.complete.call(f,b,a),c(d,f,"ajaxComplete",[b,d]),e(d)}function j(){}function k(a){return a&&(a=a.split(";",2)[0]),a&&(a==x?"html":a==w?"json":u.test(a)?"script":v.test(a)&&"xml")||"text"}function l(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function m(b){b.processData&&b.data&&"string"!=a.type(b.data)&&(b.data=a.param(b.data,b.traditional)),b.data&&(!b.type||"GET"==b.type.toUpperCase())&&(b.url=l(b.url,b.data))}function n(b,c,d,e){var f=!a.isFunction(c);return{url:b,data:f?c:void 0,success:f?a.isFunction(d)?d:void 0:c,dataType:f?e||d:d}}function o(b,c,d,e){var f,g=a.isArray(c);a.each(c,function(c,h){f=a.type(h),e&&(c=d?e:e+"["+(g?"":c)+"]"),!e&&g?b.add(h.name,h.value):"array"==f||!d&&"object"==f?o(b,h,d,c):b.add(c,h)})}var p,q,r=0,s=window.document,t=/)<[^<]*)*<\/script>/gi,u=/^(?:text|application)\/javascript/i,v=/^(?:text|application)\/xml/i,w="application/json",x="text/html",y=/^\s*$/;a.active=0,a.ajaxJSONP=function(b){if("type"in b){var c,d="jsonp"+ ++r,e=s.createElement("script"),i=function(){clearTimeout(c),a(e).remove(),delete window[d]},k=function(a){i(),a&&"timeout"!=a||(window[d]=j),h(null,a||"abort",l,b)},l={abort:k};return f(l,b)===!1?(k("abort"),!1):(window[d]=function(a){i(),g(a,l,b)},e.onerror=function(){k("error")},e.src=b.url.replace(/=\?/,"="+d),a("head").append(e),b.timeout>0&&(c=setTimeout(function(){k("timeout")},b.timeout)),l)}return a.ajax(b)},a.ajaxSettings={type:"GET",beforeSend:j,success:j,error:j,complete:j,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript",json:w,xml:"application/xml, text/xml",html:x,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},a.ajax=function(b){var c=a.extend({},b||{});for(p in a.ajaxSettings)void 0===c[p]&&(c[p]=a.ajaxSettings[p]);d(c),c.crossDomain||(c.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(c.url)&&RegExp.$2!=window.location.host),c.url||(c.url=window.location.toString()),m(c),c.cache===!1&&(c.url=l(c.url,"_="+Date.now()));var e=c.dataType,i=/=\?/.test(c.url);if("jsonp"==e||i)return i||(c.url=l(c.url,"callback=?")),a.ajaxJSONP(c);var n,o=c.accepts[e],r={},s=/^([\w-]+:)\/\//.test(c.url)?RegExp.$1:window.location.protocol,t=c.xhr();c.crossDomain||(r["X-Requested-With"]="XMLHttpRequest"),o&&(r.Accept=o,o.indexOf(",")>-1&&(o=o.split(",",2)[0]),t.overrideMimeType&&t.overrideMimeType(o)),(c.contentType||c.contentType!==!1&&c.data&&"GET"!=c.type.toUpperCase())&&(r["Content-Type"]=c.contentType||"application/x-www-form-urlencoded"),c.headers=a.extend(r,c.headers||{}),t.onreadystatechange=function(){if(4==t.readyState){t.onreadystatechange=j,clearTimeout(n);var b,d=!1;if(t.status>=200&&t.status<300||304==t.status||0==t.status&&"file:"==s){e=e||k(t.getResponseHeader("content-type")),b=t.responseText;try{"script"==e?(1,eval)(b):"xml"==e?b=t.responseXML:"json"==e&&(b=y.test(b)?null:a.parseJSON(b))}catch(f){d=f}d?h(d,"parsererror",t,c):g(b,t,c)}else h(null,t.status?"error":"abort",t,c)}};var u="async"in c?c.async:!0;t.open(c.type,c.url,u);for(q in c.headers)t.setRequestHeader(q,c.headers[q]);return f(t,c)===!1?(t.abort(),!1):(c.timeout>0&&(n=setTimeout(function(){t.onreadystatechange=j,t.abort(),h(null,"timeout",t,c)},c.timeout)),t.send(c.data?c.data:null),t)},a.get=function(b,c,d,e){return a.ajax(n.apply(null,arguments))},a.post=function(b,c,d,e){var f=n.apply(null,arguments);return f.type="POST",a.ajax(f)},a.getJSON=function(b,c,d){var e=n.apply(null,arguments);return e.dataType="json",a.ajax(e)},a.fn.load=function(b,c,d){if(!this.length)return this;var e,f=this,g=b.split(/\s/),h=n(b,c,d),i=h.success;return g.length>1&&(h.url=g[0],e=g[1]),h.success=function(b){f.html(e?a("
").html(b.replace(t,"")).find(e):b),i&&i.apply(f,arguments)},a.ajax(h),this};var z=encodeURIComponent;a.param=function(a,b){var c=[];return c.add=function(a,b){this.push(z(a)+"="+z(b))},o(c,a,b),c.join("&").replace(/%20/g,"+")}}(Zepto),function(a){a.fn.serializeArray=function(){var b,c=[];return a(Array.prototype.slice.call(this.get(0).elements)).each(function(){b=a(this);var d=b.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=d&&"reset"!=d&&"button"!=d&&("radio"!=d&&"checkbox"!=d||this.checked)&&c.push({name:b.attr("name"),value:b.val()})}),c},a.fn.serialize=function(){var a=[];return this.serializeArray().forEach(function(b){a.push(encodeURIComponent(b.name)+"="+encodeURIComponent(b.value))}),a.join("&")},a.fn.submit=function(b){if(b)this.bind("submit",b);else if(this.length){var c=a.Event("submit");this.eq(0).trigger(c),c.defaultPrevented||this.get(0).submit()}return this}}(Zepto),function(a,b){function c(a){return d(a.replace(/([a-z])([A-Z])/,"$1-$2"))}function d(a){return a.toLowerCase()}function e(a){return f?f+a:d(a)}var f,g,h,i,j,k,l,m,n="",o={Webkit:"webkit",Moz:"",O:"o",ms:"MS"},p=window.document,q=p.createElement("div"),r=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,s={};a.each(o,function(a,c){return q.style[a+"TransitionProperty"]!==b?(n="-"+d(a)+"-",f=c,!1):void 0}),g=n+"transform",s[h=n+"transition-property"]=s[i=n+"transition-duration"]=s[j=n+"transition-timing-function"]=s[k=n+"animation-name"]=s[l=n+"animation-duration"]=s[m=n+"animation-timing-function"]="",a.fx={off:f===b&&q.style.transitionProperty===b,speeds:{_default:400,fast:200,slow:600},cssPrefix:n,transitionEnd:e("TransitionEnd"),animationEnd:e("AnimationEnd")},a.fn.animate=function(b,c,d,e){return a.isPlainObject(c)&&(d=c.easing,e=c.complete,c=c.duration),c&&(c=("number"==typeof c?c:a.fx.speeds[c]||a.fx.speeds._default)/1e3),this.anim(b,c,d,e)},a.fn.anim=function(d,e,f,n){var o,p,q,t={},u="",v=this,w=a.fx.transitionEnd;if(e===b&&(e=.4),a.fx.off&&(e=0),"string"==typeof d)t[k]=d,t[l]=e+"s",t[m]=f||"linear",w=a.fx.animationEnd;else{p=[];for(o in d)r.test(o)?u+=o+"("+d[o]+") ":(t[o]=d[o],p.push(c(o)));u&&(t[g]=u,p.push(g)),e>0&&"object"==typeof d&&(t[h]=p.join(", "),t[i]=e+"s",t[j]=f||"linear")}return q=function(b){if("undefined"!=typeof b){if(b.target!==b.currentTarget)return;a(b.target).unbind(w,q)}a(this).css(s),n&&n.call(this)},e>0&&this.bind(w,q),this.size()&&this.get(0).clientLeft,this.css(t),0>=e&&setTimeout(function(){v.each(function(){q.call(this)})},0),this},q=null}(Zepto),function(a){function b(a){return"tagName"in a?a:a.parentNode}function c(a,b,c,d){var e=Math.abs(a-b),f=Math.abs(c-d);return e>=f?a-b>0?"Left":"Right":c-d>0?"Up":"Down"}function d(){j=null,k.last&&(k.el.trigger("longTap"),k={})}function e(){j&&clearTimeout(j),j=null}function f(){g&&clearTimeout(g),h&&clearTimeout(h),i&&clearTimeout(i),j&&clearTimeout(j),g=h=i=j=null,k={}}var g,h,i,j,k={},l=750;a(document).ready(function(){var m,n;a(document.body).bind("touchstart",function(c){m=Date.now(),n=m-(k.last||m),k.el=a(b(c.touches[0].target)),g&&clearTimeout(g),k.x1=c.touches[0].pageX,k.y1=c.touches[0].pageY,n>0&&250>=n&&(k.isDoubleTap=!0),k.last=m,j=setTimeout(d,l)}).bind("touchmove",function(a){e(),k.x2=a.touches[0].pageX,k.y2=a.touches[0].pageY,Math.abs(k.x1-k.x2)>10&&a.preventDefault()}).bind("touchend",function(b){e(),k.x2&&Math.abs(k.x1-k.x2)>30||k.y2&&Math.abs(k.y1-k.y2)>30?i=setTimeout(function(){k.el.trigger("swipe"),k.el.trigger("swipe"+c(k.x1,k.x2,k.y1,k.y2)),k={}},0):"last"in k&&(h=setTimeout(function(){var b=a.Event("tap");b.cancelTouch=f,k.el.trigger(b),k.isDoubleTap?(k.el.trigger("doubleTap"),k={}):g=setTimeout(function(){g=null,k.el.trigger("singleTap"),k={}},250)},0))}).bind("touchcancel",f),a(window).bind("scroll",f)}),["swipe","swipeLeft","swipeRight","swipeUp","swipeDown","doubleTap","tap","singleTap","longTap"].forEach(function(b){a.fn[b]=function(a){return this.bind(b,a)}})}(Zepto),FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),FastClick.prototype.deviceIsIOS4=FastClick.prototype.deviceIsIOS&&/OS 4_\d(_\d)?/.test(navigator.userAgent),FastClick.prototype.deviceIsIOSWithBadTarget=FastClick.prototype.deviceIsIOS&&/OS ([6-9]|\d{2})_\d/.test(navigator.userAgent),FastClick.prototype.needsClick=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"button":case"input":return this.deviceIsIOS&&"file"===a.type?!0:a.disabled;case"label":case"video":return!0;default:return/\bneedsclick\b/.test(a.className)}},FastClick.prototype.needsFocus=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(a.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!a.disabled&&!a.readOnly;default:return/\bneedsfocus\b/.test(a.className)}},FastClick.prototype.sendClick=function(a,b){"use strict";var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur(),d=b.changedTouches[0],c=document.createEvent("MouseEvents"),c.initMouseEvent("click",!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),c.forwardedTouchEvent=!0,a.dispatchEvent(c)},FastClick.prototype.focus=function(a){"use strict";var b;this.deviceIsIOS&&a.setSelectionRange?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()},FastClick.prototype.updateScrollParent=function(a){"use strict";var b,c;if(b=a.fastClickScrollParent,!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c,a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)},FastClick.prototype.getTargetElementFromEventTarget=function(a){"use strict";return a.nodeType===Node.TEXT_NODE?a.parentNode:a},FastClick.prototype.onTouchStart=function(a){"use strict";var b,c,d;if(b=this.getTargetElementFromEventTarget(a.target),c=a.targetTouches[0],this.deviceIsIOS){if(d=window.getSelection(),d.rangeCount&&!d.isCollapsed)return!0;if(!this.deviceIsIOS4){if(c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier,this.updateScrollParent(b)}}return this.trackingClick=!0,this.trackingClickStart=a.timeStamp,this.targetElement=b,this.touchStartX=c.pageX,this.touchStartY=c.pageY,a.timeStamp-this.lastClickTime<200&&a.preventDefault(),!0},FastClick.prototype.touchHasMoved=function(a){"use strict";var b=a.changedTouches[0];return Math.abs(b.pageX-this.touchStartX)>10||Math.abs(b.pageY-this.touchStartY)>10?!0:!1},FastClick.prototype.findControl=function(a){"use strict";return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},FastClick.prototype.onTouchEnd=function(a){"use strict";var b,c,d,e,f,g=this.targetElement;if(this.touchHasMoved(a)&&(this.trackingClick=!1,this.targetElement=null),!this.trackingClick)return!0;if(a.timeStamp-this.lastClickTime<200)return this.cancelNextClick=!0,!0;if(this.lastClickTime=a.timeStamp,c=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0, -this.deviceIsIOSWithBadTarget&&(f=a.changedTouches[0],g=document.elementFromPoint(f.pageX-window.pageXOffset,f.pageY-window.pageYOffset)),d=g.tagName.toLowerCase(),"label"===d){if(b=this.findControl(g)){if(this.focus(g),this.deviceIsAndroid)return!1;g=b}}else if(this.needsFocus(g))return a.timeStamp-c>100||this.deviceIsIOS&&window.top!==window&&"input"===d?(this.targetElement=null,!1):(this.focus(g),this.deviceIsIOS4&&"select"===d||(this.targetElement=null,a.preventDefault()),!1);return this.deviceIsIOS&&!this.deviceIsIOS4&&(e=g.fastClickScrollParent,e&&e.fastClickLastScrollTop!==e.scrollTop)?!0:(this.needsClick(g)||(a.preventDefault(),this.sendClick(g,a)),!1)},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onMouse=function(a){"use strict";return this.targetElement?a.forwardedTouchEvent?!0:a.cancelable&&(!this.needsClick(this.targetElement)||this.cancelNextClick)?(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1):!0:!0},FastClick.prototype.onClick=function(a){"use strict";var b;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(b=this.onMouse(a),b||(this.targetElement=null),b)},FastClick.prototype.destroy=function(){"use strict";var a=this.layer;this.deviceIsAndroid&&(a.removeEventListener("mouseover",this.onMouse,!0),a.removeEventListener("mousedown",this.onMouse,!0),a.removeEventListener("mouseup",this.onMouse,!0)),a.removeEventListener("click",this.onClick,!0),a.removeEventListener("touchstart",this.onTouchStart,!1),a.removeEventListener("touchend",this.onTouchEnd,!1),a.removeEventListener("touchcancel",this.onTouchCancel,!1)},FastClick.attach=function(a){"use strict";return new FastClick(a)},"undefined"!=typeof define&&define.amd&&define(function(){"use strict";return FastClick}),"undefined"!=typeof module&&module.exports&&(module.exports=FastClick.attach,module.exports.FastClick=FastClick);var OSUR={util:{}};OSUR.util.hasTouchEvents=function(){var a;return("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch)&&(a=!0),a},$(function(){function a(a,b){console.log(JSON.stringify(b,null,2)),0==b.state?a.removeClass("switch_on"):a.addClass("switch_on")}function b(b){console.log(JSON.stringify(b));var c,d;for(c=0;c>>0,f=0;if("function"!=typeof b)throw new TypeError;if(0==e&&1==arguments.length)throw new TypeError;if(arguments.length>=2)c=arguments[1];else for(;;){if(f in d){c=d[f++];break}if(++f>=e)throw new TypeError}for(;f0?y.fn.concat.apply([],a):a}function k(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(a){return a in H?H[a]:H[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function m(a,b){return"number"!=typeof b||J[k(a)]?b:b+"px"}function n(a){var b,c;return G[a]||(b=F.createElement(a),F.body.appendChild(b),c=I(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),"none"==c&&(c="block"),G[a]=c),G[a]}function o(a){return"children"in a?D.call(a.children):y.map(a.childNodes,function(a){if(1==a.nodeType)return a})}function p(a,b,c){for(x in b)c&&(f(b[x])||g(b[x]))?(f(b[x])&&!f(a[x])&&(a[x]={}),g(b[x])&&!g(a[x])&&(a[x]=[]),p(a[x],b[x],c)):b[x]!==w&&(a[x]=b[x])}function q(a,b){return b===w?y(a):y(a).filter(b)}function r(a,c,d,e){return b(c)?c.call(a,d,e):c}function s(a,b,c){null==c?a.removeAttribute(b):a.setAttribute(b,c)}function t(a,b){var c=a.className,d=c&&c.baseVal!==w;return b===w?d?c.baseVal:c:void(d?c.baseVal=b:a.className=b)}function u(a){var b;try{return a?"true"==a||"false"!=a&&("null"==a?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?y.parseJSON(a):a:b):a}catch(c){return a}}function v(a,b){b(a);for(var c in a.childNodes)v(a.childNodes[c],b)}var w,x,y,z,A,B,C=[],D=C.slice,E=C.filter,F=window.document,G={},H={},I=F.defaultView.getComputedStyle,J={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},K=/^\s*<(\w+|!)[^>]*>/,L=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,M=/^(?:body|html)$/i,N=["val","css","html","text","data","width","height","offset"],O=["after","prepend","before","append"],P=F.createElement("table"),Q=F.createElement("tr"),R={tr:F.createElement("tbody"),tbody:P,thead:P,tfoot:P,td:Q,th:Q,"*":F.createElement("div")},S=/complete|loaded|interactive/,T=/^\.([\w-]+)$/,U=/^#([\w-]*)$/,V=/^[\w-]+$/,W={},X=W.toString,Y={},Z=F.createElement("div");return Y.matches=function(a,b){if(!a||1!==a.nodeType)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=Z).appendChild(a),d=~Y.qsa(e,b).indexOf(a),f&&Z.removeChild(a),d},A=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},B=function(a){return E.call(a,function(b,c){return a.indexOf(b)==c})},Y.fragment=function(a,b,c){a.replace&&(a=a.replace(L,"<$1>")),b===w&&(b=K.test(a)&&RegExp.$1),b in R||(b="*");var d,e,g=R[b];return g.innerHTML=""+a,e=y.each(D.call(g.childNodes),function(){g.removeChild(this)}),f(c)&&(d=y(e),y.each(c,function(a,b){N.indexOf(a)>-1?d[a](b):d.attr(a,b)})),e},Y.Z=function(a,b){return a=a||[],a.__proto__=y.fn,a.selector=b||"",a},Y.isZ=function(a){return a instanceof Y.Z},Y.init=function(a,c){if(!a)return Y.Z();if(b(a))return y(F).ready(a);if(Y.isZ(a))return a;var d;if(g(a))d=i(a);else if(e(a))d=[f(a)?y.extend({},a):a],a=null;else if(K.test(a))d=Y.fragment(a.trim(),RegExp.$1,c),a=null;else{if(c!==w)return y(c).find(a);d=Y.qsa(F,a)}return Y.Z(d,a)},y=function(a,b){return Y.init(a,b)},y.extend=function(a){var b,c=D.call(arguments,1);return"boolean"==typeof a&&(b=a,a=c.shift()),c.forEach(function(c){p(a,c,b)}),a},Y.qsa=function(a,b){var c;return d(a)&&U.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:1!==a.nodeType&&9!==a.nodeType?[]:D.call(T.test(b)?a.getElementsByClassName(RegExp.$1):V.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},y.contains=function(a,b){return a!==b&&a.contains(b)},y.type=a,y.isFunction=b,y.isWindow=c,y.isArray=g,y.isPlainObject=f,y.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},y.inArray=function(a,b,c){return C.indexOf.call(b,a,c)},y.camelCase=A,y.trim=function(a){return a.trim()},y.uuid=0,y.support={},y.expr={},y.map=function(a,b){var c,d,e,f=[];if(h(a))for(d=0;d=0?a:a+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(a){return C.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return b(a)?this.not(this.not(a)):y(E.call(this,function(b){return Y.matches(b,a)}))},add:function(a,b){return y(B(this.concat(y(a,b))))},is:function(a){return this.length>0&&Y.matches(this[0],a)},not:function(a){var c=[];if(b(a)&&a.call!==w)this.each(function(b){a.call(this,b)||c.push(this)});else{var d="string"==typeof a?this.filter(a):h(a)&&b(a.item)?D.call(a):y(a);this.forEach(function(a){d.indexOf(a)<0&&c.push(a)})}return y(c)},has:function(a){return this.filter(function(){return e(a)?y.contains(this,a):y(this).find(a).size()})},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!e(a)?a:y(a)},last:function(){var a=this[this.length-1];return a&&!e(a)?a:y(a)},find:function(a){var b,c=this;return b="object"==typeof a?y(a).filter(function(){var a=this;return C.some.call(c,function(b){return y.contains(b,a)})}):1==this.length?y(Y.qsa(this[0],a)):this.map(function(){return Y.qsa(this,a)})},closest:function(a,b){var c=this[0],e=!1;for("object"==typeof a&&(e=y(a));c&&!(e?e.indexOf(c)>=0:Y.matches(c,a));)c=c!==b&&!d(c)&&c.parentNode;return y(c)},parents:function(a){for(var b=[],c=this;c.length>0;)c=y.map(c,function(a){if((a=a.parentNode)&&!d(a)&&b.indexOf(a)<0)return b.push(a),a});return q(b,a)},parent:function(a){return q(B(this.pluck("parentNode")),a)},children:function(a){return q(this.map(function(){return o(this)}),a)},contents:function(){return this.map(function(){return D.call(this.childNodes)})},siblings:function(a){return q(this.map(function(a,b){return E.call(o(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return y.map(this,function(b){return b[a]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=null),"none"==I(this,"").getPropertyValue("display")&&(this.style.display=n(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var c=b(a);if(this[0]&&!c)var d=y(a).get(0),e=d.parentNode||this.length>1;return this.each(function(b){y(this).wrapAll(c?a.call(this,b):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){y(this[0]).before(a=y(a));for(var b;(b=a.children()).length;)a=b.first();y(a).append(this)}return this},wrapInner:function(a){var c=b(a);return this.each(function(b){var d=y(this),e=d.contents(),f=c?a.call(this,b):a;e.length?e.wrapAll(f):d.append(f)})},unwrap:function(){return this.parent().each(function(){y(this).replaceWith(y(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(a){return this.each(function(){var b=y(this);(a===w?"none"==b.css("display"):a)?b.show():b.hide()})},prev:function(a){return y(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return y(this.pluck("nextElementSibling")).filter(a||"*")},html:function(a){return a===w?this.length>0?this[0].innerHTML:null:this.each(function(b){var c=this.innerHTML;y(this).empty().append(r(this,a,b,c))})},text:function(a){return a===w?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=a})},attr:function(a,b){var c;return"string"==typeof a&&b===w?0==this.length||1!==this[0].nodeType?w:"value"==a&&"INPUT"==this[0].nodeName?this.val():!(c=this[0].getAttribute(a))&&a in this[0]?this[0][a]:c:this.each(function(c){if(1===this.nodeType)if(e(a))for(x in a)s(this,x,a[x]);else s(this,a,r(this,b,c,this.getAttribute(a)))})},removeAttr:function(a){return this.each(function(){1===this.nodeType&&s(this,a)})},prop:function(a,b){return b===w?this[0]&&this[0][a]:this.each(function(c){this[a]=r(this,b,c,this[a])})},data:function(a,b){var c=this.attr("data-"+k(a),b);return null!==c?u(c):w},val:function(a){return a===w?this[0]&&(this[0].multiple?y(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(b){this.value=r(this,a,b,this.value)})},offset:function(a){if(a)return this.each(function(b){var c=y(this),d=r(this,a,b,c.offset()),e=c.offsetParent().offset(),f={top:d.top-e.top,left:d.left-e.left};"static"==c.css("position")&&(f.position="relative"),c.css(f)});if(0==this.length)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(b,c){if(arguments.length<2&&"string"==typeof b)return this[0]&&(this[0].style[A(b)]||I(this[0],"").getPropertyValue(b));var d="";if("string"==a(b))c||0===c?d=k(b)+":"+m(b,c):this.each(function(){this.style.removeProperty(k(b))});else for(x in b)b[x]||0===b[x]?d+=k(x)+":"+m(x,b[x])+";":this.each(function(){this.style.removeProperty(k(x))});return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(y(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return C.some.call(this,function(a){return this.test(t(a))},l(a))},addClass:function(a){return this.each(function(b){z=[];var c=t(this),d=r(this,a,b,c);d.split(/\s+/g).forEach(function(a){y(this).hasClass(a)||z.push(a)},this),z.length&&t(this,c+(c?" ":"")+z.join(" "))})},removeClass:function(a){return this.each(function(b){return a===w?t(this,""):(z=t(this),r(this,a,b,z).split(/\s+/g).forEach(function(a){z=z.replace(l(a)," ")}),t(this,z.trim()),void 0)})},toggleClass:function(a,b){return this.each(function(c){var d=y(this),e=r(this,a,c,t(this));e.split(/\s+/g).forEach(function(a){(b===w?!d.hasClass(a):b)?d.addClass(a):d.removeClass(a)})})},scrollTop:function(){if(this.length)return"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY},position:function(){if(this.length){var a=this[0],b=this.offsetParent(),c=this.offset(),d=M.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(y(a).css("margin-top"))||0,c.left-=parseFloat(y(a).css("margin-left"))||0,d.top+=parseFloat(y(b[0]).css("border-top-width"))||0,d.left+=parseFloat(y(b[0]).css("border-left-width"))||0,{top:c.top-d.top,left:c.left-d.left}}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||F.body;a&&!M.test(a.nodeName)&&"static"==y(a).css("position");)a=a.offsetParent;return a})}},y.fn.detach=y.fn.remove,["width","height"].forEach(function(a){y.fn[a]=function(b){var e,f=this[0],g=a.replace(/./,function(a){return a[0].toUpperCase()});return b===w?c(f)?f["inner"+g]:d(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[a]:this.each(function(c){f=y(this),f.css(a,r(this,b,c,f[a]()))})}}),O.forEach(function(b,c){var d=c%2;y.fn[b]=function(){var b,e,f=y.map(arguments,function(c){return b=a(c),"object"==b||"array"==b||null==c?c:Y.fragment(c)}),g=this.length>1;return f.length<1?this:this.each(function(a,b){e=d?b:b.parentNode,b=0==c?b.nextSibling:1==c?b.firstChild:2==c?b:null,f.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!e)return y(a).remove();v(e.insertBefore(a,b),function(a){null!=a.nodeName&&"SCRIPT"===a.nodeName.toUpperCase()&&(!a.type||"text/javascript"===a.type)&&!a.src&&window.eval.call(window,a.innerHTML)})})})},y.fn[d?b+"To":"insert"+(c?"Before":"After")]=function(a){return y(a)[b](this),this}}),Y.Z.prototype=y.fn,Y.uniq=B,Y.deserializeValue=u,y.zepto=Y,y}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);(c.webkit=!!d)&&(c.version=d[1]),e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function b(a){return a._zid||(a._zid=n++)}function c(a,c,f,g){if(c=d(c),c.ns)var h=e(c.ns);return(m[b(a)]||[]).filter(function(a){return a&&(!c.e||a.e==c.e)&&(!c.ns||h.test(a.ns))&&(!f||b(a.fn)===b(f))&&(!g||a.sel==g)})}function d(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function e(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function f(b,c,d){"string"!=a.type(b)?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function g(a,b){return a.del&&("focus"==a.e||"blur"==a.e)||!!b}function h(a){return p[a]||a}function i(c,e,i,j,k,l){var n=b(c),o=m[n]||(m[n]=[]);f(e,i,function(b,e){var f=d(b);f.fn=e,f.sel=j,f.e in p&&(e=function(b){var c=b.relatedTarget;if(!c||c!==this&&!a.contains(this,c))return f.fn.apply(this,arguments)}),f.del=k&&k(e,b);var i=f.del||e;f.proxy=function(a){var b=i.apply(c,[a].concat(a.data));return b===!1&&(a.preventDefault(),a.stopPropagation()),b},f.i=o.length,o.push(f),c.addEventListener(h(f.e),f.proxy,g(f,l))})}function j(a,d,e,i,j){var k=b(a);f(d||"",e,function(b,d){c(a,b,d,i).forEach(function(b){delete m[k][b.i],a.removeEventListener(h(b.e),b.proxy,g(b,j))})})}function k(b){var c,d={originalEvent:b};for(c in b)!s.test(c)&&void 0!==b[c]&&(d[c]=b[c]);return a.each(t,function(a,c){d[a]=function(){return this[c]=q,b[a].apply(b,arguments)},d[c]=r}),d}function l(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var m=(a.zepto.qsa,{}),n=1,o={},p={mouseenter:"mouseover",mouseleave:"mouseout"};o.click=o.mousedown=o.mouseup=o.mousemove="MouseEvents",a.event={add:i,remove:j},a.proxy=function(c,d){if(a.isFunction(c)){var e=function(){return c.apply(d,arguments)};return e._zid=b(c),e}if("string"==typeof d)return a.proxy(c[d],c);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){i(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){j(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){i(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return j(d,b,a),c}})})};var q=function(){return!0},r=function(){return!1},s=/^([A-Z]|layer[XY]$)/,t={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){i(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);if(g)return e=a.extend(k(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){j(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){return("string"==typeof b||a.isPlainObject(b))&&(b=a.Event(b)),l(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,d){var e,f;return this.each(function(g,h){e=k("string"==typeof b?a.Event(b):b),e.data=d,e.target=h,a.each(c(h,b.type||b),function(a,b){if(f=b.proxy(e),e.isImmediatePropagationStopped())return!1})}),f},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){"string"!=typeof a&&(b=a,a=b.type);var c=document.createEvent(o[a]||"Events"),d=!0;if(b)for(var e in b)"bubbles"==e?d=!!b[e]:c[e]=b[e];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function(a){function b(b,c,d){var e=a.Event(c);return a(b).trigger(e,d),!e.defaultPrevented}function c(a,c,d,e){if(a.global)return b(c||s,d,e)}function d(b){b.global&&0===a.active++&&c(b,null,"ajaxStart")}function e(b){b.global&&!--a.active&&c(b,null,"ajaxStop")}function f(a,b){var d=b.context;return b.beforeSend.call(d,a,b)!==!1&&c(b,d,"ajaxBeforeSend",[a,b])!==!1&&void c(b,d,"ajaxSend",[a,b])}function g(a,b,d){var e=d.context,f="success";d.success.call(e,a,f,b),c(d,e,"ajaxSuccess",[b,d,a]),i(f,b,d)}function h(a,b,d,e){var f=e.context;e.error.call(f,d,b,a),c(e,f,"ajaxError",[d,e,a]),i(b,d,e)}function i(a,b,d){var f=d.context;d.complete.call(f,b,a),c(d,f,"ajaxComplete",[b,d]),e(d)}function j(){}function k(a){return a&&(a=a.split(";",2)[0]),a&&(a==x?"html":a==w?"json":u.test(a)?"script":v.test(a)&&"xml")||"text"}function l(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function m(b){b.processData&&b.data&&"string"!=a.type(b.data)&&(b.data=a.param(b.data,b.traditional)),b.data&&(!b.type||"GET"==b.type.toUpperCase())&&(b.url=l(b.url,b.data))}function n(b,c,d,e){var f=!a.isFunction(c);return{url:b,data:f?c:void 0,success:f?a.isFunction(d)?d:void 0:c,dataType:f?e||d:d}}function o(b,c,d,e){var f,g=a.isArray(c);a.each(c,function(c,h){f=a.type(h),e&&(c=d?e:e+"["+(g?"":c)+"]"),!e&&g?b.add(h.name,h.value):"array"==f||!d&&"object"==f?o(b,h,d,c):b.add(c,h)})}var p,q,r=0,s=window.document,t=/)<[^<]*)*<\/script>/gi,u=/^(?:text|application)\/javascript/i,v=/^(?:text|application)\/xml/i,w="application/json",x="text/html",y=/^\s*$/;a.active=0,a.ajaxJSONP=function(b){if("type"in b){var c,d="jsonp"+ ++r,e=s.createElement("script"),i=function(){clearTimeout(c),a(e).remove(),delete window[d]},k=function(a){i(),a&&"timeout"!=a||(window[d]=j),h(null,a||"abort",l,b)},l={abort:k};return f(l,b)===!1?(k("abort"),!1):(window[d]=function(a){i(),g(a,l,b)},e.onerror=function(){k("error")},e.src=b.url.replace(/=\?/,"="+d),a("head").append(e),b.timeout>0&&(c=setTimeout(function(){k("timeout")},b.timeout)),l)}return a.ajax(b)},a.ajaxSettings={type:"GET",beforeSend:j,success:j,error:j,complete:j,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript",json:w,xml:"application/xml, text/xml",html:x,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},a.ajax=function(b){var c=a.extend({},b||{});for(p in a.ajaxSettings)void 0===c[p]&&(c[p]=a.ajaxSettings[p]);d(c),c.crossDomain||(c.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(c.url)&&RegExp.$2!=window.location.host),c.url||(c.url=window.location.toString()),m(c),c.cache===!1&&(c.url=l(c.url,"_="+Date.now()));var e=c.dataType,i=/=\?/.test(c.url);if("jsonp"==e||i)return i||(c.url=l(c.url,"callback=?")),a.ajaxJSONP(c);var n,o=c.accepts[e],r={},s=/^([\w-]+:)\/\//.test(c.url)?RegExp.$1:window.location.protocol,t=c.xhr();c.crossDomain||(r["X-Requested-With"]="XMLHttpRequest"),o&&(r.Accept=o,o.indexOf(",")>-1&&(o=o.split(",",2)[0]),t.overrideMimeType&&t.overrideMimeType(o)),(c.contentType||c.contentType!==!1&&c.data&&"GET"!=c.type.toUpperCase())&&(r["Content-Type"]=c.contentType||"application/x-www-form-urlencoded"),c.headers=a.extend(r,c.headers||{}),t.onreadystatechange=function(){if(4==t.readyState){t.onreadystatechange=j,clearTimeout(n);var b,d=!1;if(t.status>=200&&t.status<300||304==t.status||0==t.status&&"file:"==s){e=e||k(t.getResponseHeader("content-type")),b=t.responseText;try{"script"==e?(0,eval)(b):"xml"==e?b=t.responseXML:"json"==e&&(b=y.test(b)?null:a.parseJSON(b))}catch(f){d=f}d?h(d,"parsererror",t,c):g(b,t,c)}else h(null,t.status?"error":"abort",t,c)}};var u=!("async"in c)||c.async;t.open(c.type,c.url,u);for(q in c.headers)t.setRequestHeader(q,c.headers[q]);return f(t,c)===!1?(t.abort(),!1):(c.timeout>0&&(n=setTimeout(function(){t.onreadystatechange=j,t.abort(),h(null,"timeout",t,c)},c.timeout)),t.send(c.data?c.data:null),t)},a.get=function(b,c,d,e){return a.ajax(n.apply(null,arguments))},a.post=function(b,c,d,e){var f=n.apply(null,arguments);return f.type="POST",a.ajax(f)},a.getJSON=function(b,c,d){var e=n.apply(null,arguments);return e.dataType="json",a.ajax(e)},a.fn.load=function(b,c,d){if(!this.length)return this;var e,f=this,g=b.split(/\s/),h=n(b,c,d),i=h.success;return g.length>1&&(h.url=g[0],e=g[1]),h.success=function(b){f.html(e?a("
").html(b.replace(t,"")).find(e):b),i&&i.apply(f,arguments)},a.ajax(h),this};var z=encodeURIComponent;a.param=function(a,b){var c=[];return c.add=function(a,b){this.push(z(a)+"="+z(b))},o(c,a,b),c.join("&").replace(/%20/g,"+")}}(Zepto),function(a){a.fn.serializeArray=function(){var b,c=[];return a(Array.prototype.slice.call(this.get(0).elements)).each(function(){b=a(this);var d=b.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=d&&"reset"!=d&&"button"!=d&&("radio"!=d&&"checkbox"!=d||this.checked)&&c.push({name:b.attr("name"),value:b.val()})}),c},a.fn.serialize=function(){var a=[];return this.serializeArray().forEach(function(b){a.push(encodeURIComponent(b.name)+"="+encodeURIComponent(b.value))}),a.join("&")},a.fn.submit=function(b){if(b)this.bind("submit",b);else if(this.length){var c=a.Event("submit");this.eq(0).trigger(c),c.defaultPrevented||this.get(0).submit()}return this}}(Zepto),function(a,b){function c(a){return d(a.replace(/([a-z])([A-Z])/,"$1-$2"))}function d(a){return a.toLowerCase()}function e(a){return f?f+a:d(a)}var f,g,h,i,j,k,l,m,n="",o={Webkit:"webkit",Moz:"",O:"o",ms:"MS"},p=window.document,q=p.createElement("div"),r=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,s={};a.each(o,function(a,c){if(q.style[a+"TransitionProperty"]!==b)return n="-"+d(a)+"-",f=c,!1}),g=n+"transform",s[h=n+"transition-property"]=s[i=n+"transition-duration"]=s[j=n+"transition-timing-function"]=s[k=n+"animation-name"]=s[l=n+"animation-duration"]=s[m=n+"animation-timing-function"]="",a.fx={off:f===b&&q.style.transitionProperty===b,speeds:{_default:400,fast:200,slow:600},cssPrefix:n,transitionEnd:e("TransitionEnd"),animationEnd:e("AnimationEnd")},a.fn.animate=function(b,c,d,e){return a.isPlainObject(c)&&(d=c.easing,e=c.complete,c=c.duration),c&&(c=("number"==typeof c?c:a.fx.speeds[c]||a.fx.speeds._default)/1e3),this.anim(b,c,d,e)},a.fn.anim=function(d,e,f,n){var o,p,q,t={},u="",v=this,w=a.fx.transitionEnd;if(e===b&&(e=.4),a.fx.off&&(e=0),"string"==typeof d)t[k]=d,t[l]=e+"s",t[m]=f||"linear",w=a.fx.animationEnd;else{p=[];for(o in d)r.test(o)?u+=o+"("+d[o]+") ":(t[o]=d[o],p.push(c(o)));u&&(t[g]=u,p.push(g)),e>0&&"object"==typeof d&&(t[h]=p.join(", "),t[i]=e+"s",t[j]=f||"linear")}return q=function(b){if("undefined"!=typeof b){if(b.target!==b.currentTarget)return;a(b.target).unbind(w,q)}a(this).css(s),n&&n.call(this)},e>0&&this.bind(w,q),this.size()&&this.get(0).clientLeft,this.css(t),e<=0&&setTimeout(function(){v.each(function(){q.call(this)})},0),this},q=null}(Zepto),function(a){function b(a){return"tagName"in a?a:a.parentNode}function c(a,b,c,d){var e=Math.abs(a-b),f=Math.abs(c-d);return e>=f?a-b>0?"Left":"Right":c-d>0?"Up":"Down"}function d(){j=null,k.last&&(k.el.trigger("longTap"),k={})}function e(){j&&clearTimeout(j),j=null}function f(){g&&clearTimeout(g),h&&clearTimeout(h),i&&clearTimeout(i),j&&clearTimeout(j),g=h=i=j=null,k={}}var g,h,i,j,k={},l=750;a(document).ready(function(){var m,n;a(document.body).bind("touchstart",function(c){m=Date.now(),n=m-(k.last||m),k.el=a(b(c.touches[0].target)),g&&clearTimeout(g),k.x1=c.touches[0].pageX,k.y1=c.touches[0].pageY,n>0&&n<=250&&(k.isDoubleTap=!0),k.last=m,j=setTimeout(d,l)}).bind("touchmove",function(a){e(),k.x2=a.touches[0].pageX,k.y2=a.touches[0].pageY,Math.abs(k.x1-k.x2)>10&&a.preventDefault()}).bind("touchend",function(b){e(),k.x2&&Math.abs(k.x1-k.x2)>30||k.y2&&Math.abs(k.y1-k.y2)>30?i=setTimeout(function(){k.el.trigger("swipe"),k.el.trigger("swipe"+c(k.x1,k.x2,k.y1,k.y2)),k={}},0):"last"in k&&(h=setTimeout(function(){var b=a.Event("tap");b.cancelTouch=f,k.el.trigger(b),k.isDoubleTap?(k.el.trigger("doubleTap"),k={}):g=setTimeout(function(){g=null,k.el.trigger("singleTap"),k={}},250)},0))}).bind("touchcancel",f),a(window).bind("scroll",f)}),["swipe","swipeLeft","swipeRight","swipeUp","swipeDown","doubleTap","tap","singleTap","longTap"].forEach(function(b){a.fn[b]=function(a){return this.bind(b,a)}})}(Zepto),FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),FastClick.prototype.deviceIsIOS4=FastClick.prototype.deviceIsIOS&&/OS 4_\d(_\d)?/.test(navigator.userAgent),FastClick.prototype.deviceIsIOSWithBadTarget=FastClick.prototype.deviceIsIOS&&/OS ([6-9]|\d{2})_\d/.test(navigator.userAgent),FastClick.prototype.needsClick=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"button":case"input":return!(!this.deviceIsIOS||"file"!==a.type)||a.disabled;case"label":case"video":return!0;default:return/\bneedsclick\b/.test(a.className)}},FastClick.prototype.needsFocus=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(a.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!a.disabled&&!a.readOnly;default:return/\bneedsfocus\b/.test(a.className)}},FastClick.prototype.sendClick=function(a,b){"use strict";var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur(),d=b.changedTouches[0],c=document.createEvent("MouseEvents"),c.initMouseEvent("click",!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),c.forwardedTouchEvent=!0,a.dispatchEvent(c)},FastClick.prototype.focus=function(a){"use strict";var b;this.deviceIsIOS&&a.setSelectionRange?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()},FastClick.prototype.updateScrollParent=function(a){"use strict";var b,c;if(b=a.fastClickScrollParent,!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c,a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)},FastClick.prototype.getTargetElementFromEventTarget=function(a){"use strict";return a.nodeType===Node.TEXT_NODE?a.parentNode:a},FastClick.prototype.onTouchStart=function(a){"use strict";var b,c,d;if(b=this.getTargetElementFromEventTarget(a.target),c=a.targetTouches[0],this.deviceIsIOS){if(d=window.getSelection(),d.rangeCount&&!d.isCollapsed)return!0;if(!this.deviceIsIOS4){if(c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier,this.updateScrollParent(b)}}return this.trackingClick=!0,this.trackingClickStart=a.timeStamp,this.targetElement=b,this.touchStartX=c.pageX,this.touchStartY=c.pageY,a.timeStamp-this.lastClickTime<200&&a.preventDefault(),!0},FastClick.prototype.touchHasMoved=function(a){"use strict";var b=a.changedTouches[0];return Math.abs(b.pageX-this.touchStartX)>10||Math.abs(b.pageY-this.touchStartY)>10},FastClick.prototype.findControl=function(a){"use strict";return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},FastClick.prototype.onTouchEnd=function(a){"use strict";var b,c,d,e,f,g=this.targetElement;if(this.touchHasMoved(a)&&(this.trackingClick=!1,this.targetElement=null),!this.trackingClick)return!0;if(a.timeStamp-this.lastClickTime<200)return this.cancelNextClick=!0,!0;if(this.lastClickTime=a.timeStamp,c=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0,this.deviceIsIOSWithBadTarget&&(f=a.changedTouches[0], +g=document.elementFromPoint(f.pageX-window.pageXOffset,f.pageY-window.pageYOffset)),d=g.tagName.toLowerCase(),"label"===d){if(b=this.findControl(g)){if(this.focus(g),this.deviceIsAndroid)return!1;g=b}}else if(this.needsFocus(g))return a.timeStamp-c>100||this.deviceIsIOS&&window.top!==window&&"input"===d?(this.targetElement=null,!1):(this.focus(g),this.deviceIsIOS4&&"select"===d||(this.targetElement=null,a.preventDefault()),!1);return!(!this.deviceIsIOS||this.deviceIsIOS4||(e=g.fastClickScrollParent,!e||e.fastClickLastScrollTop===e.scrollTop))||(this.needsClick(g)||(a.preventDefault(),this.sendClick(g,a)),!1)},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onMouse=function(a){"use strict";return!this.targetElement||(!!a.forwardedTouchEvent||(!a.cancelable||(!(!this.needsClick(this.targetElement)||this.cancelNextClick)||(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1))))},FastClick.prototype.onClick=function(a){"use strict";var b;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===a.target.type&&0===a.detail||(b=this.onMouse(a),b||(this.targetElement=null),b)},FastClick.prototype.destroy=function(){"use strict";var a=this.layer;this.deviceIsAndroid&&(a.removeEventListener("mouseover",this.onMouse,!0),a.removeEventListener("mousedown",this.onMouse,!0),a.removeEventListener("mouseup",this.onMouse,!0)),a.removeEventListener("click",this.onClick,!0),a.removeEventListener("touchstart",this.onTouchStart,!1),a.removeEventListener("touchend",this.onTouchEnd,!1),a.removeEventListener("touchcancel",this.onTouchCancel,!1)},FastClick.attach=function(a){"use strict";return new FastClick(a)},"undefined"!=typeof define&&define.amd&&define(function(){"use strict";return FastClick}),"undefined"!=typeof module&&module.exports&&(module.exports=FastClick.attach,module.exports.FastClick=FastClick);var OSUR={util:{}};OSUR.util.hasTouchEvents=function(){var a;return("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch)&&(a=!0),a},$(function(){function a(a,b){console.log(JSON.stringify(b,null,2)),0==b.state?a.removeClass("switch_on"):a.addClass("switch_on")}function b(b){console.log(JSON.stringify(b));var c,d;for(c=0;c Date: Sat, 6 Jan 2018 11:13:01 +0100 Subject: [PATCH 22/27] avoid bidirectional dependency --- lib/gpio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gpio.js b/lib/gpio.js index 23613e0..18ac8cb 100644 --- a/lib/gpio.js +++ b/lib/gpio.js @@ -1,6 +1,6 @@ var DEVICE_NAME = 'gpio'; -var gpioLibrary = require('./gpio-la-mock'); var gpios = null; +var gpioLibrary = null; function fetchPinByIndex(callback, pinsToRead, pinIdx) { var pin = pinsToRead[pinIdx]; From daa0244ec4de7172a3a13b39603cd5f2aebcfeaa Mon Sep 17 00:00:00 2001 From: OvisMaximus Date: Sat, 6 Jan 2018 11:38:31 +0100 Subject: [PATCH 23/27] remove gpio lib wiring pi --- lib/gpio-la-wiringpi.js | 27 --------------------------- package.json | 1 - 2 files changed, 28 deletions(-) delete mode 100644 lib/gpio-la-wiringpi.js diff --git a/lib/gpio-la-wiringpi.js b/lib/gpio-la-wiringpi.js deleted file mode 100644 index 8803596..0000000 --- a/lib/gpio-la-wiringpi.js +++ /dev/null @@ -1,27 +0,0 @@ -// var wpi = require('wiring-pi'); -var wpi = { - setup: function () {}, - digitalRead: function () {}, - digitalWrite: function () {}, -}; - -exports.init = function (callback) { - wpi.setup('gpio'); - if (callback) callback(); -}; - -exports.getPinStateById = function (callback, pinId) { - var result = wpi.digitalRead(pinId); - if (callback) { - callback(result); - } -}; - -exports.setPinByIdToState = function (callback, pinId, newState) { - wpi.digitalWrite(pinId, newState); - if (callback) { - callback(newState); - } -}; - -exports.name = 'wiring-pi'; diff --git a/package.json b/package.json index d43224a..5e9b829 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "lirc_node": "0.0.4", "lodash": "^3.10.1", "morgan": "^1.6.1", - "gpio": "^0.2.0", "swig": "^1.4.2" }, "devDependencies": { From 65fe787d52229339d119bccebddfd4a6c09a6222 Mon Sep 17 00:00:00 2001 From: OvisMaximus Date: Sat, 6 Jan 2018 12:21:55 +0100 Subject: [PATCH 24/27] fix missing library filebased gpio --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5e9b829..289f5c9 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "compression": "^1.6.0", "consolidate": "^0.13.1", "express": "^4.13.0", + "gpio": "^0.2.7", "lirc_node": "0.0.4", "lodash": "^3.10.1", "morgan": "^1.6.1", From 6bd355b8c34474159e722789cfdfec8d658580a5 Mon Sep 17 00:00:00 2001 From: OvisMaximus Date: Sat, 6 Jan 2018 21:09:12 +0100 Subject: [PATCH 25/27] adopt convention and make callback last arg --- app.js | 8 ++++---- lib/gpio-la-gpio.js | 10 +++++----- lib/gpio-la-mock.js | 6 +++--- lib/gpio.js | 34 +++++++++++++++++----------------- static/js/compiled/app.js | 2 +- static/js/vendor/zepto.min.js | 2 +- test/lib/gpio.js | 24 ++++++++++++------------ 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/app.js b/app.js index c39f537..6a6a874 100644 --- a/app.js +++ b/app.js @@ -158,9 +158,9 @@ app.get('/remotes/:remote.json', function (req, res) { function respondWithGpioState(res) { if (config.gpios) { - gpio.updatePinStates(function (result) { + gpio.updatePinStates(config.gpios, function (result) { res.json(result); - }, config.gpios); + }); } else { res.send(404); } @@ -199,11 +199,11 @@ app.post('/remotes/:remote/:command/send_stop', function (req, res) { // toggle /gpios/:gpio_pin app.post('/gpios/:gpio_pin', function (req, res) { - gpio.togglePin(function (result) { + gpio.togglePin(req.params.gpio_pin, function (result) { res.setHeader('Cache-Control', 'no-cache'); res.json(result); res.end(); - }, req.params.gpio_pin); + }); }); diff --git a/lib/gpio-la-gpio.js b/lib/gpio-la-gpio.js index 11a6a4e..4a6d0d8 100644 --- a/lib/gpio-la-gpio.js +++ b/lib/gpio-la-gpio.js @@ -1,7 +1,7 @@ var gpioLib = require('gpio'); var pinStubs = {}; -function configurePin(callback, pinConfig) { +function configurePin(pinConfig, callback) { var stub = gpioLib.export(pinConfig.pin, { direction: 'out', interval: 500, @@ -10,7 +10,7 @@ function configurePin(callback, pinConfig) { pinStubs['' + pinConfig.pin] = stub; } -exports.init = function (callback, config) { +exports.init = function (config, callback) { var configsLeft = config.length; var i; function collector() { @@ -20,18 +20,18 @@ exports.init = function (callback, config) { } } for (i = 0; i < config.length; i++) { - configurePin(collector, config[i]); + configurePin(config[i], collector); } }; -exports.getPinStateById = function (callback, pinId) { +exports.getPinStateById = function (pinId, callback) { var result = pinStubs['' + pinId].value; if (callback) { callback(result); } }; -exports.setPinByIdToState = function (callback, pinId, newState) { +exports.setPinByIdToState = function (pinId, newState, callback) { var stub = pinStubs['' + pinId]; stub.set(newState, function () { if (callback) { diff --git a/lib/gpio-la-mock.js b/lib/gpio-la-mock.js index d14f053..5f18d24 100644 --- a/lib/gpio-la-mock.js +++ b/lib/gpio-la-mock.js @@ -1,13 +1,13 @@ module.exports = { emulatedPins: new Array(50), - getPinStateById: function (callback, pin) { + getPinStateById: function (pin, callback) { callback(this.emulatedPins[pin]); }, - setPinByIdToState: function (callback, pin, state) { + setPinByIdToState: function (pin, state, callback) { this.emulatedPins[pin] = state; callback(this.emulatedPins[pin]); }, - init: function (callback) { + init: function (config, callback) { this.initPinsWith(0); if (callback) { callback(); diff --git a/lib/gpio.js b/lib/gpio.js index 18ac8cb..9539ae3 100644 --- a/lib/gpio.js +++ b/lib/gpio.js @@ -2,15 +2,15 @@ var DEVICE_NAME = 'gpio'; var gpios = null; var gpioLibrary = null; -function fetchPinByIndex(callback, pinsToRead, pinIdx) { +function fetchPinByIndex(pinsToRead, pinIdx, callback) { var pin = pinsToRead[pinIdx]; - gpioLibrary.getPinStateById(function (result) { + gpioLibrary.getPinStateById(pin.pin, function (result) { pin.state = result; callback(); - }, pin.pin, callback); + }); } -function updatePinStates(callback, pinsToRead) { +function updatePinStates(pinsToRead, callback) { var toCollect = pinsToRead.length; var collector = function () { if (--toCollect === 0) { @@ -23,24 +23,24 @@ function updatePinStates(callback, pinsToRead) { var i; if (pinsToRead) { for (i = 0; i < pinsToRead.length; i++) { - fetchPinByIndex(collector, pinsToRead, i); + fetchPinByIndex(pinsToRead, i, collector); } } } -function togglePin(callback, pinId) { +function togglePin(pinId, callback) { var numericPinId = parseInt(pinId, 10); - gpioLibrary.getPinStateById(function (currentState) { + gpioLibrary.getPinStateById(numericPinId, function (currentState) { var newState = currentState > 0 ? 0 : 1; - gpioLibrary.setPinByIdToState(function (result) { + gpioLibrary.setPinByIdToState(numericPinId, newState, function (result) { if (callback) { callback({ pin: numericPinId, state: result, }); } - }, numericPinId, newState); - }, numericPinId); + }); + }); } function findElement(array, propertyName, propertyValue) { @@ -57,22 +57,22 @@ function getPinIdByName(pinName) { return gpioPin.pin; } -function setPin(callback, pinName, newState) { +function setPin(pinName, newState, callback) { var numericPinId = getPinIdByName(pinName); - gpioLibrary.setPinByIdToState(callback, numericPinId, newState); + gpioLibrary.setPinByIdToState(numericPinId, newState, callback); } function init(configuration, callback) { if (configuration) { gpios = configuration; - gpioLibrary.init(function () { - updatePinStates(function (result) { + gpioLibrary.init(configuration, function () { + updatePinStates(gpios, function (result) { gpios = result; if (callback) { callback(); } - }, gpios); - }, configuration); + }); + }); } } @@ -81,7 +81,7 @@ function setGpioLibrary(newGpioLibrary) { } function call(done, command) { - setPin(function () {}, command[1], command[2]); + setPin(command[1], command[2], function () {}); done(); } diff --git a/static/js/compiled/app.js b/static/js/compiled/app.js index 209f767..e204801 100644 --- a/static/js/compiled/app.js +++ b/static/js/compiled/app.js @@ -1,2 +1,2 @@ -function FastClick(a){"use strict";var b,c=this;if(this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.layer=a,!a||!a.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){return FastClick.prototype.onClick.apply(c,arguments)},this.onMouse=function(){return FastClick.prototype.onMouse.apply(c,arguments)},this.onTouchStart=function(){return FastClick.prototype.onTouchStart.apply(c,arguments)},this.onTouchEnd=function(){return FastClick.prototype.onTouchEnd.apply(c,arguments)},this.onTouchCancel=function(){return FastClick.prototype.onTouchCancel.apply(c,arguments)},"undefined"!=typeof window.ontouchstart&&(this.deviceIsAndroid&&(a.addEventListener("mouseover",this.onMouse,!0),a.addEventListener("mousedown",this.onMouse,!0),a.addEventListener("mouseup",this.onMouse,!0)),a.addEventListener("click",this.onClick,!0),a.addEventListener("touchstart",this.onTouchStart,!1),a.addEventListener("touchend",this.onTouchEnd,!1),a.addEventListener("touchcancel",this.onTouchCancel,!1),Event.prototype.stopImmediatePropagation||(a.removeEventListener=function(b,c,d){var e=Node.prototype.removeEventListener;"click"===b?e.call(a,b,c.hijacked||c,d):e.call(a,b,c,d)},a.addEventListener=function(b,c,d){var e=Node.prototype.addEventListener;"click"===b?e.call(a,b,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),d):e.call(a,b,c,d)}),"function"==typeof a.onclick&&(b=a.onclick,a.addEventListener("click",function(a){b(a)},!1),a.onclick=null))}!function(a){String.prototype.trim===a&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.reduce===a&&(Array.prototype.reduce=function(b){if(void 0===this||null===this)throw new TypeError;var c,d=Object(this),e=d.length>>>0,f=0;if("function"!=typeof b)throw new TypeError;if(0==e&&1==arguments.length)throw new TypeError;if(arguments.length>=2)c=arguments[1];else for(;;){if(f in d){c=d[f++];break}if(++f>=e)throw new TypeError}for(;f0?y.fn.concat.apply([],a):a}function k(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(a){return a in H?H[a]:H[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function m(a,b){return"number"!=typeof b||J[k(a)]?b:b+"px"}function n(a){var b,c;return G[a]||(b=F.createElement(a),F.body.appendChild(b),c=I(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),"none"==c&&(c="block"),G[a]=c),G[a]}function o(a){return"children"in a?D.call(a.children):y.map(a.childNodes,function(a){if(1==a.nodeType)return a})}function p(a,b,c){for(x in b)c&&(f(b[x])||g(b[x]))?(f(b[x])&&!f(a[x])&&(a[x]={}),g(b[x])&&!g(a[x])&&(a[x]=[]),p(a[x],b[x],c)):b[x]!==w&&(a[x]=b[x])}function q(a,b){return b===w?y(a):y(a).filter(b)}function r(a,c,d,e){return b(c)?c.call(a,d,e):c}function s(a,b,c){null==c?a.removeAttribute(b):a.setAttribute(b,c)}function t(a,b){var c=a.className,d=c&&c.baseVal!==w;return b===w?d?c.baseVal:c:void(d?c.baseVal=b:a.className=b)}function u(a){var b;try{return a?"true"==a||"false"!=a&&("null"==a?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?y.parseJSON(a):a:b):a}catch(c){return a}}function v(a,b){b(a);for(var c in a.childNodes)v(a.childNodes[c],b)}var w,x,y,z,A,B,C=[],D=C.slice,E=C.filter,F=window.document,G={},H={},I=F.defaultView.getComputedStyle,J={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},K=/^\s*<(\w+|!)[^>]*>/,L=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,M=/^(?:body|html)$/i,N=["val","css","html","text","data","width","height","offset"],O=["after","prepend","before","append"],P=F.createElement("table"),Q=F.createElement("tr"),R={tr:F.createElement("tbody"),tbody:P,thead:P,tfoot:P,td:Q,th:Q,"*":F.createElement("div")},S=/complete|loaded|interactive/,T=/^\.([\w-]+)$/,U=/^#([\w-]*)$/,V=/^[\w-]+$/,W={},X=W.toString,Y={},Z=F.createElement("div");return Y.matches=function(a,b){if(!a||1!==a.nodeType)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=Z).appendChild(a),d=~Y.qsa(e,b).indexOf(a),f&&Z.removeChild(a),d},A=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},B=function(a){return E.call(a,function(b,c){return a.indexOf(b)==c})},Y.fragment=function(a,b,c){a.replace&&(a=a.replace(L,"<$1>")),b===w&&(b=K.test(a)&&RegExp.$1),b in R||(b="*");var d,e,g=R[b];return g.innerHTML=""+a,e=y.each(D.call(g.childNodes),function(){g.removeChild(this)}),f(c)&&(d=y(e),y.each(c,function(a,b){N.indexOf(a)>-1?d[a](b):d.attr(a,b)})),e},Y.Z=function(a,b){return a=a||[],a.__proto__=y.fn,a.selector=b||"",a},Y.isZ=function(a){return a instanceof Y.Z},Y.init=function(a,c){if(!a)return Y.Z();if(b(a))return y(F).ready(a);if(Y.isZ(a))return a;var d;if(g(a))d=i(a);else if(e(a))d=[f(a)?y.extend({},a):a],a=null;else if(K.test(a))d=Y.fragment(a.trim(),RegExp.$1,c),a=null;else{if(c!==w)return y(c).find(a);d=Y.qsa(F,a)}return Y.Z(d,a)},y=function(a,b){return Y.init(a,b)},y.extend=function(a){var b,c=D.call(arguments,1);return"boolean"==typeof a&&(b=a,a=c.shift()),c.forEach(function(c){p(a,c,b)}),a},Y.qsa=function(a,b){var c;return d(a)&&U.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:1!==a.nodeType&&9!==a.nodeType?[]:D.call(T.test(b)?a.getElementsByClassName(RegExp.$1):V.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},y.contains=function(a,b){return a!==b&&a.contains(b)},y.type=a,y.isFunction=b,y.isWindow=c,y.isArray=g,y.isPlainObject=f,y.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},y.inArray=function(a,b,c){return C.indexOf.call(b,a,c)},y.camelCase=A,y.trim=function(a){return a.trim()},y.uuid=0,y.support={},y.expr={},y.map=function(a,b){var c,d,e,f=[];if(h(a))for(d=0;d=0?a:a+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(a){return C.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return b(a)?this.not(this.not(a)):y(E.call(this,function(b){return Y.matches(b,a)}))},add:function(a,b){return y(B(this.concat(y(a,b))))},is:function(a){return this.length>0&&Y.matches(this[0],a)},not:function(a){var c=[];if(b(a)&&a.call!==w)this.each(function(b){a.call(this,b)||c.push(this)});else{var d="string"==typeof a?this.filter(a):h(a)&&b(a.item)?D.call(a):y(a);this.forEach(function(a){d.indexOf(a)<0&&c.push(a)})}return y(c)},has:function(a){return this.filter(function(){return e(a)?y.contains(this,a):y(this).find(a).size()})},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!e(a)?a:y(a)},last:function(){var a=this[this.length-1];return a&&!e(a)?a:y(a)},find:function(a){var b,c=this;return b="object"==typeof a?y(a).filter(function(){var a=this;return C.some.call(c,function(b){return y.contains(b,a)})}):1==this.length?y(Y.qsa(this[0],a)):this.map(function(){return Y.qsa(this,a)})},closest:function(a,b){var c=this[0],e=!1;for("object"==typeof a&&(e=y(a));c&&!(e?e.indexOf(c)>=0:Y.matches(c,a));)c=c!==b&&!d(c)&&c.parentNode;return y(c)},parents:function(a){for(var b=[],c=this;c.length>0;)c=y.map(c,function(a){if((a=a.parentNode)&&!d(a)&&b.indexOf(a)<0)return b.push(a),a});return q(b,a)},parent:function(a){return q(B(this.pluck("parentNode")),a)},children:function(a){return q(this.map(function(){return o(this)}),a)},contents:function(){return this.map(function(){return D.call(this.childNodes)})},siblings:function(a){return q(this.map(function(a,b){return E.call(o(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return y.map(this,function(b){return b[a]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=null),"none"==I(this,"").getPropertyValue("display")&&(this.style.display=n(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var c=b(a);if(this[0]&&!c)var d=y(a).get(0),e=d.parentNode||this.length>1;return this.each(function(b){y(this).wrapAll(c?a.call(this,b):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){y(this[0]).before(a=y(a));for(var b;(b=a.children()).length;)a=b.first();y(a).append(this)}return this},wrapInner:function(a){var c=b(a);return this.each(function(b){var d=y(this),e=d.contents(),f=c?a.call(this,b):a;e.length?e.wrapAll(f):d.append(f)})},unwrap:function(){return this.parent().each(function(){y(this).replaceWith(y(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(a){return this.each(function(){var b=y(this);(a===w?"none"==b.css("display"):a)?b.show():b.hide()})},prev:function(a){return y(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return y(this.pluck("nextElementSibling")).filter(a||"*")},html:function(a){return a===w?this.length>0?this[0].innerHTML:null:this.each(function(b){var c=this.innerHTML;y(this).empty().append(r(this,a,b,c))})},text:function(a){return a===w?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=a})},attr:function(a,b){var c;return"string"==typeof a&&b===w?0==this.length||1!==this[0].nodeType?w:"value"==a&&"INPUT"==this[0].nodeName?this.val():!(c=this[0].getAttribute(a))&&a in this[0]?this[0][a]:c:this.each(function(c){if(1===this.nodeType)if(e(a))for(x in a)s(this,x,a[x]);else s(this,a,r(this,b,c,this.getAttribute(a)))})},removeAttr:function(a){return this.each(function(){1===this.nodeType&&s(this,a)})},prop:function(a,b){return b===w?this[0]&&this[0][a]:this.each(function(c){this[a]=r(this,b,c,this[a])})},data:function(a,b){var c=this.attr("data-"+k(a),b);return null!==c?u(c):w},val:function(a){return a===w?this[0]&&(this[0].multiple?y(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(b){this.value=r(this,a,b,this.value)})},offset:function(a){if(a)return this.each(function(b){var c=y(this),d=r(this,a,b,c.offset()),e=c.offsetParent().offset(),f={top:d.top-e.top,left:d.left-e.left};"static"==c.css("position")&&(f.position="relative"),c.css(f)});if(0==this.length)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(b,c){if(arguments.length<2&&"string"==typeof b)return this[0]&&(this[0].style[A(b)]||I(this[0],"").getPropertyValue(b));var d="";if("string"==a(b))c||0===c?d=k(b)+":"+m(b,c):this.each(function(){this.style.removeProperty(k(b))});else for(x in b)b[x]||0===b[x]?d+=k(x)+":"+m(x,b[x])+";":this.each(function(){this.style.removeProperty(k(x))});return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(y(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return C.some.call(this,function(a){return this.test(t(a))},l(a))},addClass:function(a){return this.each(function(b){z=[];var c=t(this),d=r(this,a,b,c);d.split(/\s+/g).forEach(function(a){y(this).hasClass(a)||z.push(a)},this),z.length&&t(this,c+(c?" ":"")+z.join(" "))})},removeClass:function(a){return this.each(function(b){return a===w?t(this,""):(z=t(this),r(this,a,b,z).split(/\s+/g).forEach(function(a){z=z.replace(l(a)," ")}),t(this,z.trim()),void 0)})},toggleClass:function(a,b){return this.each(function(c){var d=y(this),e=r(this,a,c,t(this));e.split(/\s+/g).forEach(function(a){(b===w?!d.hasClass(a):b)?d.addClass(a):d.removeClass(a)})})},scrollTop:function(){if(this.length)return"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY},position:function(){if(this.length){var a=this[0],b=this.offsetParent(),c=this.offset(),d=M.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(y(a).css("margin-top"))||0,c.left-=parseFloat(y(a).css("margin-left"))||0,d.top+=parseFloat(y(b[0]).css("border-top-width"))||0,d.left+=parseFloat(y(b[0]).css("border-left-width"))||0,{top:c.top-d.top,left:c.left-d.left}}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||F.body;a&&!M.test(a.nodeName)&&"static"==y(a).css("position");)a=a.offsetParent;return a})}},y.fn.detach=y.fn.remove,["width","height"].forEach(function(a){y.fn[a]=function(b){var e,f=this[0],g=a.replace(/./,function(a){return a[0].toUpperCase()});return b===w?c(f)?f["inner"+g]:d(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[a]:this.each(function(c){f=y(this),f.css(a,r(this,b,c,f[a]()))})}}),O.forEach(function(b,c){var d=c%2;y.fn[b]=function(){var b,e,f=y.map(arguments,function(c){return b=a(c),"object"==b||"array"==b||null==c?c:Y.fragment(c)}),g=this.length>1;return f.length<1?this:this.each(function(a,b){e=d?b:b.parentNode,b=0==c?b.nextSibling:1==c?b.firstChild:2==c?b:null,f.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!e)return y(a).remove();v(e.insertBefore(a,b),function(a){null!=a.nodeName&&"SCRIPT"===a.nodeName.toUpperCase()&&(!a.type||"text/javascript"===a.type)&&!a.src&&window.eval.call(window,a.innerHTML)})})})},y.fn[d?b+"To":"insert"+(c?"Before":"After")]=function(a){return y(a)[b](this),this}}),Y.Z.prototype=y.fn,Y.uniq=B,Y.deserializeValue=u,y.zepto=Y,y}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);(c.webkit=!!d)&&(c.version=d[1]),e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function b(a){return a._zid||(a._zid=n++)}function c(a,c,f,g){if(c=d(c),c.ns)var h=e(c.ns);return(m[b(a)]||[]).filter(function(a){return a&&(!c.e||a.e==c.e)&&(!c.ns||h.test(a.ns))&&(!f||b(a.fn)===b(f))&&(!g||a.sel==g)})}function d(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function e(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function f(b,c,d){"string"!=a.type(b)?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function g(a,b){return a.del&&("focus"==a.e||"blur"==a.e)||!!b}function h(a){return p[a]||a}function i(c,e,i,j,k,l){var n=b(c),o=m[n]||(m[n]=[]);f(e,i,function(b,e){var f=d(b);f.fn=e,f.sel=j,f.e in p&&(e=function(b){var c=b.relatedTarget;if(!c||c!==this&&!a.contains(this,c))return f.fn.apply(this,arguments)}),f.del=k&&k(e,b);var i=f.del||e;f.proxy=function(a){var b=i.apply(c,[a].concat(a.data));return b===!1&&(a.preventDefault(),a.stopPropagation()),b},f.i=o.length,o.push(f),c.addEventListener(h(f.e),f.proxy,g(f,l))})}function j(a,d,e,i,j){var k=b(a);f(d||"",e,function(b,d){c(a,b,d,i).forEach(function(b){delete m[k][b.i],a.removeEventListener(h(b.e),b.proxy,g(b,j))})})}function k(b){var c,d={originalEvent:b};for(c in b)!s.test(c)&&void 0!==b[c]&&(d[c]=b[c]);return a.each(t,function(a,c){d[a]=function(){return this[c]=q,b[a].apply(b,arguments)},d[c]=r}),d}function l(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var m=(a.zepto.qsa,{}),n=1,o={},p={mouseenter:"mouseover",mouseleave:"mouseout"};o.click=o.mousedown=o.mouseup=o.mousemove="MouseEvents",a.event={add:i,remove:j},a.proxy=function(c,d){if(a.isFunction(c)){var e=function(){return c.apply(d,arguments)};return e._zid=b(c),e}if("string"==typeof d)return a.proxy(c[d],c);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){i(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){j(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){i(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return j(d,b,a),c}})})};var q=function(){return!0},r=function(){return!1},s=/^([A-Z]|layer[XY]$)/,t={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){i(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);if(g)return e=a.extend(k(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){j(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){return("string"==typeof b||a.isPlainObject(b))&&(b=a.Event(b)),l(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,d){var e,f;return this.each(function(g,h){e=k("string"==typeof b?a.Event(b):b),e.data=d,e.target=h,a.each(c(h,b.type||b),function(a,b){if(f=b.proxy(e),e.isImmediatePropagationStopped())return!1})}),f},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){"string"!=typeof a&&(b=a,a=b.type);var c=document.createEvent(o[a]||"Events"),d=!0;if(b)for(var e in b)"bubbles"==e?d=!!b[e]:c[e]=b[e];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function(a){function b(b,c,d){var e=a.Event(c);return a(b).trigger(e,d),!e.defaultPrevented}function c(a,c,d,e){if(a.global)return b(c||s,d,e)}function d(b){b.global&&0===a.active++&&c(b,null,"ajaxStart")}function e(b){b.global&&!--a.active&&c(b,null,"ajaxStop")}function f(a,b){var d=b.context;return b.beforeSend.call(d,a,b)!==!1&&c(b,d,"ajaxBeforeSend",[a,b])!==!1&&void c(b,d,"ajaxSend",[a,b])}function g(a,b,d){var e=d.context,f="success";d.success.call(e,a,f,b),c(d,e,"ajaxSuccess",[b,d,a]),i(f,b,d)}function h(a,b,d,e){var f=e.context;e.error.call(f,d,b,a),c(e,f,"ajaxError",[d,e,a]),i(b,d,e)}function i(a,b,d){var f=d.context;d.complete.call(f,b,a),c(d,f,"ajaxComplete",[b,d]),e(d)}function j(){}function k(a){return a&&(a=a.split(";",2)[0]),a&&(a==x?"html":a==w?"json":u.test(a)?"script":v.test(a)&&"xml")||"text"}function l(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function m(b){b.processData&&b.data&&"string"!=a.type(b.data)&&(b.data=a.param(b.data,b.traditional)),b.data&&(!b.type||"GET"==b.type.toUpperCase())&&(b.url=l(b.url,b.data))}function n(b,c,d,e){var f=!a.isFunction(c);return{url:b,data:f?c:void 0,success:f?a.isFunction(d)?d:void 0:c,dataType:f?e||d:d}}function o(b,c,d,e){var f,g=a.isArray(c);a.each(c,function(c,h){f=a.type(h),e&&(c=d?e:e+"["+(g?"":c)+"]"),!e&&g?b.add(h.name,h.value):"array"==f||!d&&"object"==f?o(b,h,d,c):b.add(c,h)})}var p,q,r=0,s=window.document,t=/)<[^<]*)*<\/script>/gi,u=/^(?:text|application)\/javascript/i,v=/^(?:text|application)\/xml/i,w="application/json",x="text/html",y=/^\s*$/;a.active=0,a.ajaxJSONP=function(b){if("type"in b){var c,d="jsonp"+ ++r,e=s.createElement("script"),i=function(){clearTimeout(c),a(e).remove(),delete window[d]},k=function(a){i(),a&&"timeout"!=a||(window[d]=j),h(null,a||"abort",l,b)},l={abort:k};return f(l,b)===!1?(k("abort"),!1):(window[d]=function(a){i(),g(a,l,b)},e.onerror=function(){k("error")},e.src=b.url.replace(/=\?/,"="+d),a("head").append(e),b.timeout>0&&(c=setTimeout(function(){k("timeout")},b.timeout)),l)}return a.ajax(b)},a.ajaxSettings={type:"GET",beforeSend:j,success:j,error:j,complete:j,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript",json:w,xml:"application/xml, text/xml",html:x,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},a.ajax=function(b){var c=a.extend({},b||{});for(p in a.ajaxSettings)void 0===c[p]&&(c[p]=a.ajaxSettings[p]);d(c),c.crossDomain||(c.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(c.url)&&RegExp.$2!=window.location.host),c.url||(c.url=window.location.toString()),m(c),c.cache===!1&&(c.url=l(c.url,"_="+Date.now()));var e=c.dataType,i=/=\?/.test(c.url);if("jsonp"==e||i)return i||(c.url=l(c.url,"callback=?")),a.ajaxJSONP(c);var n,o=c.accepts[e],r={},s=/^([\w-]+:)\/\//.test(c.url)?RegExp.$1:window.location.protocol,t=c.xhr();c.crossDomain||(r["X-Requested-With"]="XMLHttpRequest"),o&&(r.Accept=o,o.indexOf(",")>-1&&(o=o.split(",",2)[0]),t.overrideMimeType&&t.overrideMimeType(o)),(c.contentType||c.contentType!==!1&&c.data&&"GET"!=c.type.toUpperCase())&&(r["Content-Type"]=c.contentType||"application/x-www-form-urlencoded"),c.headers=a.extend(r,c.headers||{}),t.onreadystatechange=function(){if(4==t.readyState){t.onreadystatechange=j,clearTimeout(n);var b,d=!1;if(t.status>=200&&t.status<300||304==t.status||0==t.status&&"file:"==s){e=e||k(t.getResponseHeader("content-type")),b=t.responseText;try{"script"==e?(0,eval)(b):"xml"==e?b=t.responseXML:"json"==e&&(b=y.test(b)?null:a.parseJSON(b))}catch(f){d=f}d?h(d,"parsererror",t,c):g(b,t,c)}else h(null,t.status?"error":"abort",t,c)}};var u=!("async"in c)||c.async;t.open(c.type,c.url,u);for(q in c.headers)t.setRequestHeader(q,c.headers[q]);return f(t,c)===!1?(t.abort(),!1):(c.timeout>0&&(n=setTimeout(function(){t.onreadystatechange=j,t.abort(),h(null,"timeout",t,c)},c.timeout)),t.send(c.data?c.data:null),t)},a.get=function(b,c,d,e){return a.ajax(n.apply(null,arguments))},a.post=function(b,c,d,e){var f=n.apply(null,arguments);return f.type="POST",a.ajax(f)},a.getJSON=function(b,c,d){var e=n.apply(null,arguments);return e.dataType="json",a.ajax(e)},a.fn.load=function(b,c,d){if(!this.length)return this;var e,f=this,g=b.split(/\s/),h=n(b,c,d),i=h.success;return g.length>1&&(h.url=g[0],e=g[1]),h.success=function(b){f.html(e?a("
").html(b.replace(t,"")).find(e):b),i&&i.apply(f,arguments)},a.ajax(h),this};var z=encodeURIComponent;a.param=function(a,b){var c=[];return c.add=function(a,b){this.push(z(a)+"="+z(b))},o(c,a,b),c.join("&").replace(/%20/g,"+")}}(Zepto),function(a){a.fn.serializeArray=function(){var b,c=[];return a(Array.prototype.slice.call(this.get(0).elements)).each(function(){b=a(this);var d=b.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=d&&"reset"!=d&&"button"!=d&&("radio"!=d&&"checkbox"!=d||this.checked)&&c.push({name:b.attr("name"),value:b.val()})}),c},a.fn.serialize=function(){var a=[];return this.serializeArray().forEach(function(b){a.push(encodeURIComponent(b.name)+"="+encodeURIComponent(b.value))}),a.join("&")},a.fn.submit=function(b){if(b)this.bind("submit",b);else if(this.length){var c=a.Event("submit");this.eq(0).trigger(c),c.defaultPrevented||this.get(0).submit()}return this}}(Zepto),function(a,b){function c(a){return d(a.replace(/([a-z])([A-Z])/,"$1-$2"))}function d(a){return a.toLowerCase()}function e(a){return f?f+a:d(a)}var f,g,h,i,j,k,l,m,n="",o={Webkit:"webkit",Moz:"",O:"o",ms:"MS"},p=window.document,q=p.createElement("div"),r=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,s={};a.each(o,function(a,c){if(q.style[a+"TransitionProperty"]!==b)return n="-"+d(a)+"-",f=c,!1}),g=n+"transform",s[h=n+"transition-property"]=s[i=n+"transition-duration"]=s[j=n+"transition-timing-function"]=s[k=n+"animation-name"]=s[l=n+"animation-duration"]=s[m=n+"animation-timing-function"]="",a.fx={off:f===b&&q.style.transitionProperty===b,speeds:{_default:400,fast:200,slow:600},cssPrefix:n,transitionEnd:e("TransitionEnd"),animationEnd:e("AnimationEnd")},a.fn.animate=function(b,c,d,e){return a.isPlainObject(c)&&(d=c.easing,e=c.complete,c=c.duration),c&&(c=("number"==typeof c?c:a.fx.speeds[c]||a.fx.speeds._default)/1e3),this.anim(b,c,d,e)},a.fn.anim=function(d,e,f,n){var o,p,q,t={},u="",v=this,w=a.fx.transitionEnd;if(e===b&&(e=.4),a.fx.off&&(e=0),"string"==typeof d)t[k]=d,t[l]=e+"s",t[m]=f||"linear",w=a.fx.animationEnd;else{p=[];for(o in d)r.test(o)?u+=o+"("+d[o]+") ":(t[o]=d[o],p.push(c(o)));u&&(t[g]=u,p.push(g)),e>0&&"object"==typeof d&&(t[h]=p.join(", "),t[i]=e+"s",t[j]=f||"linear")}return q=function(b){if("undefined"!=typeof b){if(b.target!==b.currentTarget)return;a(b.target).unbind(w,q)}a(this).css(s),n&&n.call(this)},e>0&&this.bind(w,q),this.size()&&this.get(0).clientLeft,this.css(t),e<=0&&setTimeout(function(){v.each(function(){q.call(this)})},0),this},q=null}(Zepto),function(a){function b(a){return"tagName"in a?a:a.parentNode}function c(a,b,c,d){var e=Math.abs(a-b),f=Math.abs(c-d);return e>=f?a-b>0?"Left":"Right":c-d>0?"Up":"Down"}function d(){j=null,k.last&&(k.el.trigger("longTap"),k={})}function e(){j&&clearTimeout(j),j=null}function f(){g&&clearTimeout(g),h&&clearTimeout(h),i&&clearTimeout(i),j&&clearTimeout(j),g=h=i=j=null,k={}}var g,h,i,j,k={},l=750;a(document).ready(function(){var m,n;a(document.body).bind("touchstart",function(c){m=Date.now(),n=m-(k.last||m),k.el=a(b(c.touches[0].target)),g&&clearTimeout(g),k.x1=c.touches[0].pageX,k.y1=c.touches[0].pageY,n>0&&n<=250&&(k.isDoubleTap=!0),k.last=m,j=setTimeout(d,l)}).bind("touchmove",function(a){e(),k.x2=a.touches[0].pageX,k.y2=a.touches[0].pageY,Math.abs(k.x1-k.x2)>10&&a.preventDefault()}).bind("touchend",function(b){e(),k.x2&&Math.abs(k.x1-k.x2)>30||k.y2&&Math.abs(k.y1-k.y2)>30?i=setTimeout(function(){k.el.trigger("swipe"),k.el.trigger("swipe"+c(k.x1,k.x2,k.y1,k.y2)),k={}},0):"last"in k&&(h=setTimeout(function(){var b=a.Event("tap");b.cancelTouch=f,k.el.trigger(b),k.isDoubleTap?(k.el.trigger("doubleTap"),k={}):g=setTimeout(function(){g=null,k.el.trigger("singleTap"),k={}},250)},0))}).bind("touchcancel",f),a(window).bind("scroll",f)}),["swipe","swipeLeft","swipeRight","swipeUp","swipeDown","doubleTap","tap","singleTap","longTap"].forEach(function(b){a.fn[b]=function(a){return this.bind(b,a)}})}(Zepto),FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),FastClick.prototype.deviceIsIOS4=FastClick.prototype.deviceIsIOS&&/OS 4_\d(_\d)?/.test(navigator.userAgent),FastClick.prototype.deviceIsIOSWithBadTarget=FastClick.prototype.deviceIsIOS&&/OS ([6-9]|\d{2})_\d/.test(navigator.userAgent),FastClick.prototype.needsClick=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"button":case"input":return!(!this.deviceIsIOS||"file"!==a.type)||a.disabled;case"label":case"video":return!0;default:return/\bneedsclick\b/.test(a.className)}},FastClick.prototype.needsFocus=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(a.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!a.disabled&&!a.readOnly;default:return/\bneedsfocus\b/.test(a.className)}},FastClick.prototype.sendClick=function(a,b){"use strict";var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur(),d=b.changedTouches[0],c=document.createEvent("MouseEvents"),c.initMouseEvent("click",!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),c.forwardedTouchEvent=!0,a.dispatchEvent(c)},FastClick.prototype.focus=function(a){"use strict";var b;this.deviceIsIOS&&a.setSelectionRange?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()},FastClick.prototype.updateScrollParent=function(a){"use strict";var b,c;if(b=a.fastClickScrollParent,!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c,a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)},FastClick.prototype.getTargetElementFromEventTarget=function(a){"use strict";return a.nodeType===Node.TEXT_NODE?a.parentNode:a},FastClick.prototype.onTouchStart=function(a){"use strict";var b,c,d;if(b=this.getTargetElementFromEventTarget(a.target),c=a.targetTouches[0],this.deviceIsIOS){if(d=window.getSelection(),d.rangeCount&&!d.isCollapsed)return!0;if(!this.deviceIsIOS4){if(c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier,this.updateScrollParent(b)}}return this.trackingClick=!0,this.trackingClickStart=a.timeStamp,this.targetElement=b,this.touchStartX=c.pageX,this.touchStartY=c.pageY,a.timeStamp-this.lastClickTime<200&&a.preventDefault(),!0},FastClick.prototype.touchHasMoved=function(a){"use strict";var b=a.changedTouches[0];return Math.abs(b.pageX-this.touchStartX)>10||Math.abs(b.pageY-this.touchStartY)>10},FastClick.prototype.findControl=function(a){"use strict";return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},FastClick.prototype.onTouchEnd=function(a){"use strict";var b,c,d,e,f,g=this.targetElement;if(this.touchHasMoved(a)&&(this.trackingClick=!1,this.targetElement=null),!this.trackingClick)return!0;if(a.timeStamp-this.lastClickTime<200)return this.cancelNextClick=!0,!0;if(this.lastClickTime=a.timeStamp,c=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0,this.deviceIsIOSWithBadTarget&&(f=a.changedTouches[0], +function FastClick(a){"use strict";var b,c=this;if(this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.layer=a,!a||!a.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){return FastClick.prototype.onClick.apply(c,arguments)},this.onMouse=function(){return FastClick.prototype.onMouse.apply(c,arguments)},this.onTouchStart=function(){return FastClick.prototype.onTouchStart.apply(c,arguments)},this.onTouchEnd=function(){return FastClick.prototype.onTouchEnd.apply(c,arguments)},this.onTouchCancel=function(){return FastClick.prototype.onTouchCancel.apply(c,arguments)},"undefined"!=typeof window.ontouchstart&&(this.deviceIsAndroid&&(a.addEventListener("mouseover",this.onMouse,!0),a.addEventListener("mousedown",this.onMouse,!0),a.addEventListener("mouseup",this.onMouse,!0)),a.addEventListener("click",this.onClick,!0),a.addEventListener("touchstart",this.onTouchStart,!1),a.addEventListener("touchend",this.onTouchEnd,!1),a.addEventListener("touchcancel",this.onTouchCancel,!1),Event.prototype.stopImmediatePropagation||(a.removeEventListener=function(b,c,d){var e=Node.prototype.removeEventListener;"click"===b?e.call(a,b,c.hijacked||c,d):e.call(a,b,c,d)},a.addEventListener=function(b,c,d){var e=Node.prototype.addEventListener;"click"===b?e.call(a,b,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),d):e.call(a,b,c,d)}),"function"==typeof a.onclick&&(b=a.onclick,a.addEventListener("click",function(a){b(a)},!1),a.onclick=null))}!function(a){String.prototype.trim===a&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.reduce===a&&(Array.prototype.reduce=function(b){if(void 0===this||null===this)throw new TypeError;var c,d=Object(this),e=d.length>>>0,f=0;if("function"!=typeof b)throw new TypeError;if(0==e&&1==arguments.length)throw new TypeError;if(arguments.length>=2)c=arguments[1];else for(;;){if(f in d){c=d[f++];break}if(++f>=e)throw new TypeError}for(;f0?y.fn.concat.apply([],a):a}function k(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(a){return a in H?H[a]:H[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function m(a,b){return"number"!=typeof b||J[k(a)]?b:b+"px"}function n(a){var b,c;return G[a]||(b=F.createElement(a),F.body.appendChild(b),c=I(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),"none"==c&&(c="block"),G[a]=c),G[a]}function o(a){return"children"in a?D.call(a.children):y.map(a.childNodes,function(a){if(1==a.nodeType)return a})}function p(a,b,c){for(x in b)c&&(f(b[x])||g(b[x]))?(f(b[x])&&!f(a[x])&&(a[x]={}),g(b[x])&&!g(a[x])&&(a[x]=[]),p(a[x],b[x],c)):b[x]!==w&&(a[x]=b[x])}function q(a,b){return b===w?y(a):y(a).filter(b)}function r(a,c,d,e){return b(c)?c.call(a,d,e):c}function s(a,b,c){null==c?a.removeAttribute(b):a.setAttribute(b,c)}function t(a,b){var c=a.className,d=c&&c.baseVal!==w;return b===w?d?c.baseVal:c:void(d?c.baseVal=b:a.className=b)}function u(a){var b;try{return a?"true"==a||"false"!=a&&("null"==a?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?y.parseJSON(a):a:b):a}catch(c){return a}}function v(a,b){b(a);for(var c in a.childNodes)v(a.childNodes[c],b)}var w,x,y,z,A,B,C=[],D=C.slice,E=C.filter,F=window.document,G={},H={},I=F.defaultView.getComputedStyle,J={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},K=/^\s*<(\w+|!)[^>]*>/,L=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,M=/^(?:body|html)$/i,N=["val","css","html","text","data","width","height","offset"],O=["after","prepend","before","append"],P=F.createElement("table"),Q=F.createElement("tr"),R={tr:F.createElement("tbody"),tbody:P,thead:P,tfoot:P,td:Q,th:Q,"*":F.createElement("div")},S=/complete|loaded|interactive/,T=/^\.([\w-]+)$/,U=/^#([\w-]*)$/,V=/^[\w-]+$/,W={},X=W.toString,Y={},Z=F.createElement("div");return Y.matches=function(a,b){if(!a||1!==a.nodeType)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=Z).appendChild(a),d=~Y.qsa(e,b).indexOf(a),f&&Z.removeChild(a),d},A=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},B=function(a){return E.call(a,function(b,c){return a.indexOf(b)==c})},Y.fragment=function(a,b,c){a.replace&&(a=a.replace(L,"<$1>")),b===w&&(b=K.test(a)&&RegExp.$1),b in R||(b="*");var d,e,g=R[b];return g.innerHTML=""+a,e=y.each(D.call(g.childNodes),function(){g.removeChild(this)}),f(c)&&(d=y(e),y.each(c,function(a,b){N.indexOf(a)>-1?d[a](b):d.attr(a,b)})),e},Y.Z=function(a,b){return a=a||[],a.__proto__=y.fn,a.selector=b||"",a},Y.isZ=function(a){return a instanceof Y.Z},Y.init=function(a,c){if(!a)return Y.Z();if(b(a))return y(F).ready(a);if(Y.isZ(a))return a;var d;if(g(a))d=i(a);else if(e(a))d=[f(a)?y.extend({},a):a],a=null;else if(K.test(a))d=Y.fragment(a.trim(),RegExp.$1,c),a=null;else{if(c!==w)return y(c).find(a);d=Y.qsa(F,a)}return Y.Z(d,a)},y=function(a,b){return Y.init(b, a)},y.extend=function(a){var b,c=D.call(arguments,1);return"boolean"==typeof a&&(b=a,a=c.shift()),c.forEach(function(c){p(a,c,b)}),a},Y.qsa=function(a, b){var c;return d(a)&&U.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:1!==a.nodeType&&9!==a.nodeType?[]:D.call(T.test(b)?a.getElementsByClassName(RegExp.$1):V.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},y.contains=function(a, b){return a!==b&&a.contains(b)},y.type=a,y.isFunction=b,y.isWindow=c,y.isArray=g,y.isPlainObject=f,y.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},y.inArray=function(a,b,c){return C.indexOf.call(b,a,c)},y.camelCase=A,y.trim=function(a){return a.trim()},y.uuid=0,y.support={},y.expr={},y.map=function(a,b){var c,d,e,f=[];if(h(a))for(d=0;d=0?a:a+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(a){return C.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return b(a)?this.not(this.not(a)):y(E.call(this,function(b){return Y.matches(b,a)}))},add:function(a,b){return y(B(this.concat(y(a,b))))},is:function(a){return this.length>0&&Y.matches(this[0],a)},not:function(a){var c=[];if(b(a)&&a.call!==w)this.each(function(b){a.call(this,b)||c.push(this)});else{var d="string"==typeof a?this.filter(a):h(a)&&b(a.item)?D.call(a):y(a);this.forEach(function(a){d.indexOf(a)<0&&c.push(a)})}return y(c)},has:function(a){return this.filter(function(){return e(a)?y.contains(this,a):y(this).find(a).size()})},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!e(a)?a:y(a)},last:function(){var a=this[this.length-1];return a&&!e(a)?a:y(a)},find:function(a){var b,c=this;return b="object"==typeof a?y(a).filter(function(){var a=this;return C.some.call(c,function(b){return y.contains(b,a)})}):1==this.length?y(Y.qsa(this[0],a)):this.map(function(){return Y.qsa(this,a)})},closest:function(a,b){var c=this[0],e=!1;for("object"==typeof a&&(e=y(a));c&&!(e?e.indexOf(c)>=0:Y.matches(c,a));)c=c!==b&&!d(c)&&c.parentNode;return y(c)},parents:function(a){for(var b=[],c=this;c.length>0;)c=y.map(c,function(a){if((a=a.parentNode)&&!d(a)&&b.indexOf(a)<0)return b.push(a),a});return q(b,a)},parent:function(a){return q(B(this.pluck("parentNode")),a)},children:function(a){return q(this.map(function(){return o(this)}),a)},contents:function(){return this.map(function(){return D.call(this.childNodes)})},siblings:function(a){return q(this.map(function(a,b){return E.call(o(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return y.map(this,function(b){return b[a]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=null),"none"==I(this,"").getPropertyValue("display")&&(this.style.display=n(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var c=b(a);if(this[0]&&!c)var d=y(a).get(0),e=d.parentNode||this.length>1;return this.each(function(b){y(this).wrapAll(c?a.call(this,b):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){y(this[0]).before(a=y(a));for(var b;(b=a.children()).length;)a=b.first();y(a).append(this)}return this},wrapInner:function(a){var c=b(a);return this.each(function(b){var d=y(this),e=d.contents(),f=c?a.call(this,b):a;e.length?e.wrapAll(f):d.append(f)})},unwrap:function(){return this.parent().each(function(){y(this).replaceWith(y(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(a){return this.each(function(){var b=y(this);(a===w?"none"==b.css("display"):a)?b.show():b.hide()})},prev:function(a){return y(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return y(this.pluck("nextElementSibling")).filter(a||"*")},html:function(a){return a===w?this.length>0?this[0].innerHTML:null:this.each(function(b){var c=this.innerHTML;y(this).empty().append(r(this,a,b,c))})},text:function(a){return a===w?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=a})},attr:function(a,b){var c;return"string"==typeof a&&b===w?0==this.length||1!==this[0].nodeType?w:"value"==a&&"INPUT"==this[0].nodeName?this.val():!(c=this[0].getAttribute(a))&&a in this[0]?this[0][a]:c:this.each(function(c){if(1===this.nodeType)if(e(a))for(x in a)s(this,x,a[x]);else s(this,a,r(this,b,c,this.getAttribute(a)))})},removeAttr:function(a){return this.each(function(){1===this.nodeType&&s(this,a)})},prop:function(a,b){return b===w?this[0]&&this[0][a]:this.each(function(c){this[a]=r(this,b,c,this[a])})},data:function(a,b){var c=this.attr("data-"+k(a),b);return null!==c?u(c):w},val:function(a){return a===w?this[0]&&(this[0].multiple?y(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(b){this.value=r(this,a,b,this.value)})},offset:function(a){if(a)return this.each(function(b){var c=y(this),d=r(this,a,b,c.offset()),e=c.offsetParent().offset(),f={top:d.top-e.top,left:d.left-e.left};"static"==c.css("position")&&(f.position="relative"),c.css(f)});if(0==this.length)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(b,c){if(arguments.length<2&&"string"==typeof b)return this[0]&&(this[0].style[A(b)]||I(this[0],"").getPropertyValue(b));var d="";if("string"==a(b))c||0===c?d=k(b)+":"+m(b,c):this.each(function(){this.style.removeProperty(k(b))});else for(x in b)b[x]||0===b[x]?d+=k(x)+":"+m(x,b[x])+";":this.each(function(){this.style.removeProperty(k(x))});return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(y(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return C.some.call(this,function(a){return this.test(t(a))},l(a))},addClass:function(a){return this.each(function(b){z=[];var c=t(this),d=r(this,a,b,c);d.split(/\s+/g).forEach(function(a){y(this).hasClass(a)||z.push(a)},this),z.length&&t(this,c+(c?" ":"")+z.join(" "))})},removeClass:function(a){return this.each(function(b){return a===w?t(this,""):(z=t(this),r(this,a,b,z).split(/\s+/g).forEach(function(a){z=z.replace(l(a)," ")}),t(this,z.trim()),void 0)})},toggleClass:function(a,b){return this.each(function(c){var d=y(this),e=r(this,a,c,t(this));e.split(/\s+/g).forEach(function(a){(b===w?!d.hasClass(a):b)?d.addClass(a):d.removeClass(a)})})},scrollTop:function(){if(this.length)return"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY},position:function(){if(this.length){var a=this[0],b=this.offsetParent(),c=this.offset(),d=M.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(y(a).css("margin-top"))||0,c.left-=parseFloat(y(a).css("margin-left"))||0,d.top+=parseFloat(y(b[0]).css("border-top-width"))||0,d.left+=parseFloat(y(b[0]).css("border-left-width"))||0,{top:c.top-d.top,left:c.left-d.left}}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||F.body;a&&!M.test(a.nodeName)&&"static"==y(a).css("position");)a=a.offsetParent;return a})}},y.fn.detach=y.fn.remove,["width","height"].forEach(function(a){y.fn[a]=function(b){var e,f=this[0],g=a.replace(/./,function(a){return a[0].toUpperCase()});return b===w?c(f)?f["inner"+g]:d(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[a]:this.each(function(c){f=y(this),f.css(a,r(this,b,c,f[a]()))})}}),O.forEach(function(b,c){var d=c%2;y.fn[b]=function(){var b,e,f=y.map(arguments,function(c){return b=a(c),"object"==b||"array"==b||null==c?c:Y.fragment(c)}),g=this.length>1;return f.length<1?this:this.each(function(a,b){e=d?b:b.parentNode,b=0==c?b.nextSibling:1==c?b.firstChild:2==c?b:null,f.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!e)return y(a).remove();v(e.insertBefore(a,b),function(a){null!=a.nodeName&&"SCRIPT"===a.nodeName.toUpperCase()&&(!a.type||"text/javascript"===a.type)&&!a.src&&window.eval.call(window,a.innerHTML)})})})},y.fn[d?b+"To":"insert"+(c?"Before":"After")]=function(a){return y(a)[b](this),this}}),Y.Z.prototype=y.fn,Y.uniq=B,Y.deserializeValue=u,y.zepto=Y,y}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);(c.webkit=!!d)&&(c.version=d[1]),e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function b(a){return a._zid||(a._zid=n++)}function c(a,c,f,g){if(c=d(c),c.ns)var h=e(c.ns);return(m[b(a)]||[]).filter(function(a){return a&&(!c.e||a.e==c.e)&&(!c.ns||h.test(a.ns))&&(!f||b(a.fn)===b(f))&&(!g||a.sel==g)})}function d(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function e(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function f(b,c,d){"string"!=a.type(b)?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function g(a,b){return a.del&&("focus"==a.e||"blur"==a.e)||!!b}function h(a){return p[a]||a}function i(c,e,i,j,k,l){var n=b(c),o=m[n]||(m[n]=[]);f(e,i,function(b,e){var f=d(b);f.fn=e,f.sel=j,f.e in p&&(e=function(b){var c=b.relatedTarget;if(!c||c!==this&&!a.contains(this,c))return f.fn.apply(this,arguments)}),f.del=k&&k(e,b);var i=f.del||e;f.proxy=function(a){var b=i.apply(c,[a].concat(a.data));return b===!1&&(a.preventDefault(),a.stopPropagation()),b},f.i=o.length,o.push(f),c.addEventListener(h(f.e),f.proxy,g(f,l))})}function j(a,d,e,i,j){var k=b(a);f(d||"",e,function(b,d){c(a,b,d,i).forEach(function(b){delete m[k][b.i],a.removeEventListener(h(b.e),b.proxy,g(b,j))})})}function k(b){var c,d={originalEvent:b};for(c in b)!s.test(c)&&void 0!==b[c]&&(d[c]=b[c]);return a.each(t,function(a,c){d[a]=function(){return this[c]=q,b[a].apply(b,arguments)},d[c]=r}),d}function l(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var m=(a.zepto.qsa,{}),n=1,o={},p={mouseenter:"mouseover",mouseleave:"mouseout"};o.click=o.mousedown=o.mouseup=o.mousemove="MouseEvents",a.event={add:i,remove:j},a.proxy=function(c,d){if(a.isFunction(c)){var e=function(){return c.apply(d,arguments)};return e._zid=b(c),e}if("string"==typeof d)return a.proxy(c[d],c);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){i(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){j(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){i(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return j(d,b,a),c}})})};var q=function(){return!0},r=function(){return!1},s=/^([A-Z]|layer[XY]$)/,t={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){i(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);if(g)return e=a.extend(k(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){j(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){return("string"==typeof b||a.isPlainObject(b))&&(b=a.Event(b)),l(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,d){var e,f;return this.each(function(g,h){e=k("string"==typeof b?a.Event(b):b),e.data=d,e.target=h,a.each(c(h,b.type||b),function(a,b){if(f=b.proxy(e),e.isImmediatePropagationStopped())return!1})}),f},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){"string"!=typeof a&&(b=a,a=b.type);var c=document.createEvent(o[a]||"Events"),d=!0;if(b)for(var e in b)"bubbles"==e?d=!!b[e]:c[e]=b[e];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function(a){function b(b,c,d){var e=a.Event(c);return a(b).trigger(e,d),!e.defaultPrevented}function c(a,c,d,e){if(a.global)return b(c||s,d,e)}function d(b){b.global&&0===a.active++&&c(b,null,"ajaxStart")}function e(b){b.global&&!--a.active&&c(b,null,"ajaxStop")}function f(a,b){var d=b.context;return b.beforeSend.call(d,a,b)!==!1&&c(b,d,"ajaxBeforeSend",[a,b])!==!1&&void c(b,d,"ajaxSend",[a,b])}function g(a,b,d){var e=d.context,f="success";d.success.call(e,a,f,b),c(d,e,"ajaxSuccess",[b,d,a]),i(f,b,d)}function h(a,b,d,e){var f=e.context;e.error.call(f,d,b,a),c(e,f,"ajaxError",[d,e,a]),i(b,d,e)}function i(a,b,d){var f=d.context;d.complete.call(f,b,a),c(d,f,"ajaxComplete",[b,d]),e(d)}function j(){}function k(a){return a&&(a=a.split(";",2)[0]),a&&(a==x?"html":a==w?"json":u.test(a)?"script":v.test(a)&&"xml")||"text"}function l(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function m(b){b.processData&&b.data&&"string"!=a.type(b.data)&&(b.data=a.param(b.data,b.traditional)),b.data&&(!b.type||"GET"==b.type.toUpperCase())&&(b.url=l(b.url,b.data))}function n(b,c,d,e){var f=!a.isFunction(c);return{url:b,data:f?c:void 0,success:f?a.isFunction(d)?d:void 0:c,dataType:f?e||d:d}}function o(b,c,d,e){var f,g=a.isArray(c);a.each(c,function(c,h){f=a.type(h),e&&(c=d?e:e+"["+(g?"":c)+"]"),!e&&g?b.add(h.name,h.value):"array"==f||!d&&"object"==f?o(b,h,d,c):b.add(c,h)})}var p,q,r=0,s=window.document,t=/)<[^<]*)*<\/script>/gi,u=/^(?:text|application)\/javascript/i,v=/^(?:text|application)\/xml/i,w="application/json",x="text/html",y=/^\s*$/;a.active=0,a.ajaxJSONP=function(b){if("type"in b){var c,d="jsonp"+ ++r,e=s.createElement("script"),i=function(){clearTimeout(c),a(e).remove(),delete window[d]},k=function(a){i(),a&&"timeout"!=a||(window[d]=j),h(null,a||"abort",l,b)},l={abort:k};return f(l,b)===!1?(k("abort"),!1):(window[d]=function(a){i(),g(a,l,b)},e.onerror=function(){k("error")},e.src=b.url.replace(/=\?/,"="+d),a("head").append(e),b.timeout>0&&(c=setTimeout(function(){k("timeout")},b.timeout)),l)}return a.ajax(b)},a.ajaxSettings={type:"GET",beforeSend:j,success:j,error:j,complete:j,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript",json:w,xml:"application/xml, text/xml",html:x,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},a.ajax=function(b){var c=a.extend({},b||{});for(p in a.ajaxSettings)void 0===c[p]&&(c[p]=a.ajaxSettings[p]);d(c),c.crossDomain||(c.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(c.url)&&RegExp.$2!=window.location.host),c.url||(c.url=window.location.toString()),m(c),c.cache===!1&&(c.url=l(c.url,"_="+Date.now()));var e=c.dataType,i=/=\?/.test(c.url);if("jsonp"==e||i)return i||(c.url=l(c.url,"callback=?")),a.ajaxJSONP(c);var n,o=c.accepts[e],r={},s=/^([\w-]+:)\/\//.test(c.url)?RegExp.$1:window.location.protocol,t=c.xhr();c.crossDomain||(r["X-Requested-With"]="XMLHttpRequest"),o&&(r.Accept=o,o.indexOf(",")>-1&&(o=o.split(",",2)[0]),t.overrideMimeType&&t.overrideMimeType(o)),(c.contentType||c.contentType!==!1&&c.data&&"GET"!=c.type.toUpperCase())&&(r["Content-Type"]=c.contentType||"application/x-www-form-urlencoded"),c.headers=a.extend(r,c.headers||{}),t.onreadystatechange=function(){if(4==t.readyState){t.onreadystatechange=j,clearTimeout(n);var b,d=!1;if(t.status>=200&&t.status<300||304==t.status||0==t.status&&"file:"==s){e=e||k(t.getResponseHeader("content-type")),b=t.responseText;try{"script"==e?(0,eval)(b):"xml"==e?b=t.responseXML:"json"==e&&(b=y.test(b)?null:a.parseJSON(b))}catch(f){d=f}d?h(d,"parsererror",t,c):g(b,t,c)}else h(null,t.status?"error":"abort",t,c)}};var u=!("async"in c)||c.async;t.open(c.type,c.url,u);for(q in c.headers)t.setRequestHeader(q,c.headers[q]);return f(t,c)===!1?(t.abort(),!1):(c.timeout>0&&(n=setTimeout(function(){t.onreadystatechange=j,t.abort(),h(null,"timeout",t,c)},c.timeout)),t.send(c.data?c.data:null),t)},a.get=function(b,c,d,e){return a.ajax(n.apply(null,arguments))},a.post=function(b,c,d,e){var f=n.apply(null,arguments);return f.type="POST",a.ajax(f)},a.getJSON=function(b,c,d){var e=n.apply(null,arguments);return e.dataType="json",a.ajax(e)},a.fn.load=function(b,c,d){if(!this.length)return this;var e,f=this,g=b.split(/\s/),h=n(b,c,d),i=h.success;return g.length>1&&(h.url=g[0],e=g[1]),h.success=function(b){f.html(e?a("
").html(b.replace(t,"")).find(e):b),i&&i.apply(f,arguments)},a.ajax(h),this};var z=encodeURIComponent;a.param=function(a,b){var c=[];return c.add=function(a,b){this.push(z(a)+"="+z(b))},o(c,a,b),c.join("&").replace(/%20/g,"+")}}(Zepto),function(a){a.fn.serializeArray=function(){var b,c=[];return a(Array.prototype.slice.call(this.get(0).elements)).each(function(){b=a(this);var d=b.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=d&&"reset"!=d&&"button"!=d&&("radio"!=d&&"checkbox"!=d||this.checked)&&c.push({name:b.attr("name"),value:b.val()})}),c},a.fn.serialize=function(){var a=[];return this.serializeArray().forEach(function(b){a.push(encodeURIComponent(b.name)+"="+encodeURIComponent(b.value))}),a.join("&")},a.fn.submit=function(b){if(b)this.bind("submit",b);else if(this.length){var c=a.Event("submit");this.eq(0).trigger(c),c.defaultPrevented||this.get(0).submit()}return this}}(Zepto),function(a,b){function c(a){return d(a.replace(/([a-z])([A-Z])/,"$1-$2"))}function d(a){return a.toLowerCase()}function e(a){return f?f+a:d(a)}var f,g,h,i,j,k,l,m,n="",o={Webkit:"webkit",Moz:"",O:"o",ms:"MS"},p=window.document,q=p.createElement("div"),r=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,s={};a.each(o,function(a,c){if(q.style[a+"TransitionProperty"]!==b)return n="-"+d(a)+"-",f=c,!1}),g=n+"transform",s[h=n+"transition-property"]=s[i=n+"transition-duration"]=s[j=n+"transition-timing-function"]=s[k=n+"animation-name"]=s[l=n+"animation-duration"]=s[m=n+"animation-timing-function"]="",a.fx={off:f===b&&q.style.transitionProperty===b,speeds:{_default:400,fast:200,slow:600},cssPrefix:n,transitionEnd:e("TransitionEnd"),animationEnd:e("AnimationEnd")},a.fn.animate=function(b,c,d,e){return a.isPlainObject(c)&&(d=c.easing,e=c.complete,c=c.duration),c&&(c=("number"==typeof c?c:a.fx.speeds[c]||a.fx.speeds._default)/1e3),this.anim(b,c,d,e)},a.fn.anim=function(d,e,f,n){var o,p,q,t={},u="",v=this,w=a.fx.transitionEnd;if(e===b&&(e=.4),a.fx.off&&(e=0),"string"==typeof d)t[k]=d,t[l]=e+"s",t[m]=f||"linear",w=a.fx.animationEnd;else{p=[];for(o in d)r.test(o)?u+=o+"("+d[o]+") ":(t[o]=d[o],p.push(c(o)));u&&(t[g]=u,p.push(g)),e>0&&"object"==typeof d&&(t[h]=p.join(", "),t[i]=e+"s",t[j]=f||"linear")}return q=function(b){if("undefined"!=typeof b){if(b.target!==b.currentTarget)return;a(b.target).unbind(w,q)}a(this).css(s),n&&n.call(this)},e>0&&this.bind(w,q),this.size()&&this.get(0).clientLeft,this.css(t),e<=0&&setTimeout(function(){v.each(function(){q.call(this)})},0),this},q=null}(Zepto),function(a){function b(a){return"tagName"in a?a:a.parentNode}function c(a,b,c,d){var e=Math.abs(a-b),f=Math.abs(c-d);return e>=f?a-b>0?"Left":"Right":c-d>0?"Up":"Down"}function d(){j=null,k.last&&(k.el.trigger("longTap"),k={})}function e(){j&&clearTimeout(j),j=null}function f(){g&&clearTimeout(g),h&&clearTimeout(h),i&&clearTimeout(i),j&&clearTimeout(j),g=h=i=j=null,k={}}var g,h,i,j,k={},l=750;a(document).ready(function(){var m,n;a(document.body).bind("touchstart",function(c){m=Date.now(),n=m-(k.last||m),k.el=a(b(c.touches[0].target)),g&&clearTimeout(g),k.x1=c.touches[0].pageX,k.y1=c.touches[0].pageY,n>0&&n<=250&&(k.isDoubleTap=!0),k.last=m,j=setTimeout(d,l)}).bind("touchmove",function(a){e(),k.x2=a.touches[0].pageX,k.y2=a.touches[0].pageY,Math.abs(k.x1-k.x2)>10&&a.preventDefault()}).bind("touchend",function(b){e(),k.x2&&Math.abs(k.x1-k.x2)>30||k.y2&&Math.abs(k.y1-k.y2)>30?i=setTimeout(function(){k.el.trigger("swipe"),k.el.trigger("swipe"+c(k.x1,k.x2,k.y1,k.y2)),k={}},0):"last"in k&&(h=setTimeout(function(){var b=a.Event("tap");b.cancelTouch=f,k.el.trigger(b),k.isDoubleTap?(k.el.trigger("doubleTap"),k={}):g=setTimeout(function(){g=null,k.el.trigger("singleTap"),k={}},250)},0))}).bind("touchcancel",f),a(window).bind("scroll",f)}),["swipe","swipeLeft","swipeRight","swipeUp","swipeDown","doubleTap","tap","singleTap","longTap"].forEach(function(b){a.fn[b]=function(a){return this.bind(b,a)}})}(Zepto),FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),FastClick.prototype.deviceIsIOS4=FastClick.prototype.deviceIsIOS&&/OS 4_\d(_\d)?/.test(navigator.userAgent),FastClick.prototype.deviceIsIOSWithBadTarget=FastClick.prototype.deviceIsIOS&&/OS ([6-9]|\d{2})_\d/.test(navigator.userAgent),FastClick.prototype.needsClick=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"button":case"input":return!(!this.deviceIsIOS||"file"!==a.type)||a.disabled;case"label":case"video":return!0;default:return/\bneedsclick\b/.test(a.className)}},FastClick.prototype.needsFocus=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(a.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!a.disabled&&!a.readOnly;default:return/\bneedsfocus\b/.test(a.className)}},FastClick.prototype.sendClick=function(a,b){"use strict";var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur(),d=b.changedTouches[0],c=document.createEvent("MouseEvents"),c.initMouseEvent("click",!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),c.forwardedTouchEvent=!0,a.dispatchEvent(c)},FastClick.prototype.focus=function(a){"use strict";var b;this.deviceIsIOS&&a.setSelectionRange?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()},FastClick.prototype.updateScrollParent=function(a){"use strict";var b,c;if(b=a.fastClickScrollParent,!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c,a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)},FastClick.prototype.getTargetElementFromEventTarget=function(a){"use strict";return a.nodeType===Node.TEXT_NODE?a.parentNode:a},FastClick.prototype.onTouchStart=function(a){"use strict";var b,c,d;if(b=this.getTargetElementFromEventTarget(a.target),c=a.targetTouches[0],this.deviceIsIOS){if(d=window.getSelection(),d.rangeCount&&!d.isCollapsed)return!0;if(!this.deviceIsIOS4){if(c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier,this.updateScrollParent(b)}}return this.trackingClick=!0,this.trackingClickStart=a.timeStamp,this.targetElement=b,this.touchStartX=c.pageX,this.touchStartY=c.pageY,a.timeStamp-this.lastClickTime<200&&a.preventDefault(),!0},FastClick.prototype.touchHasMoved=function(a){"use strict";var b=a.changedTouches[0];return Math.abs(b.pageX-this.touchStartX)>10||Math.abs(b.pageY-this.touchStartY)>10},FastClick.prototype.findControl=function(a){"use strict";return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},FastClick.prototype.onTouchEnd=function(a){"use strict";var b,c,d,e,f,g=this.targetElement;if(this.touchHasMoved(a)&&(this.trackingClick=!1,this.targetElement=null),!this.trackingClick)return!0;if(a.timeStamp-this.lastClickTime<200)return this.cancelNextClick=!0,!0;if(this.lastClickTime=a.timeStamp,c=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0,this.deviceIsIOSWithBadTarget&&(f=a.changedTouches[0], g=document.elementFromPoint(f.pageX-window.pageXOffset,f.pageY-window.pageYOffset)),d=g.tagName.toLowerCase(),"label"===d){if(b=this.findControl(g)){if(this.focus(g),this.deviceIsAndroid)return!1;g=b}}else if(this.needsFocus(g))return a.timeStamp-c>100||this.deviceIsIOS&&window.top!==window&&"input"===d?(this.targetElement=null,!1):(this.focus(g),this.deviceIsIOS4&&"select"===d||(this.targetElement=null,a.preventDefault()),!1);return!(!this.deviceIsIOS||this.deviceIsIOS4||(e=g.fastClickScrollParent,!e||e.fastClickLastScrollTop===e.scrollTop))||(this.needsClick(g)||(a.preventDefault(),this.sendClick(g,a)),!1)},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onMouse=function(a){"use strict";return!this.targetElement||(!!a.forwardedTouchEvent||(!a.cancelable||(!(!this.needsClick(this.targetElement)||this.cancelNextClick)||(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1))))},FastClick.prototype.onClick=function(a){"use strict";var b;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===a.target.type&&0===a.detail||(b=this.onMouse(a),b||(this.targetElement=null),b)},FastClick.prototype.destroy=function(){"use strict";var a=this.layer;this.deviceIsAndroid&&(a.removeEventListener("mouseover",this.onMouse,!0),a.removeEventListener("mousedown",this.onMouse,!0),a.removeEventListener("mouseup",this.onMouse,!0)),a.removeEventListener("click",this.onClick,!0),a.removeEventListener("touchstart",this.onTouchStart,!1),a.removeEventListener("touchend",this.onTouchEnd,!1),a.removeEventListener("touchcancel",this.onTouchCancel,!1)},FastClick.attach=function(a){"use strict";return new FastClick(a)},"undefined"!=typeof define&&define.amd&&define(function(){"use strict";return FastClick}),"undefined"!=typeof module&&module.exports&&(module.exports=FastClick.attach,module.exports.FastClick=FastClick);var OSUR={util:{}};OSUR.util.hasTouchEvents=function(){var a;return("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch)&&(a=!0),a},$(function(){function a(a,b){console.log(JSON.stringify(b,null,2)),0==b.state?a.removeClass("switch_on"):a.addClass("switch_on")}function b(b){console.log(JSON.stringify(b));var c,d;for(c=0;c>>0,e=0,f;if(typeof b!="function")throw new TypeError;if(d==0&&arguments.length==1)throw new TypeError;if(arguments.length>=2)f=arguments[1];else do{if(e in c){f=c[e++];break}if(++e>=d)throw new TypeError}while(!0);while(e0?c.fn.concat.apply([],a):a}function O(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function P(a){return a in j?j[a]:j[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function Q(a,b){return typeof b=="number"&&!l[O(a)]?b+"px":b}function R(a){var b,c;return i[a]||(b=h.createElement(a),h.body.appendChild(b),c=k(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),c=="none"&&(c="block"),i[a]=c),i[a]}function S(a){return"children"in a?f.call(a.children):c.map(a.childNodes,function(a){if(a.nodeType==1)return a})}function T(c,d,e){for(b in d)e&&(J(d[b])||K(d[b]))?(J(d[b])&&!J(c[b])&&(c[b]={}),K(d[b])&&!K(c[b])&&(c[b]=[]),T(c[b],d[b],e)):d[b]!==a&&(c[b]=d[b])}function U(b,d){return d===a?c(b):c(b).filter(d)}function V(a,b,c,d){return F(b)?b.call(a,c,d):b}function W(a,b,c){c==null?a.removeAttribute(b):a.setAttribute(b,c)}function X(b,c){var d=b.className,e=d&&d.baseVal!==a;if(c===a)return e?d.baseVal:d;e?d.baseVal=c:b.className=c}function Y(a){var b;try{return a?a=="true"||(a=="false"?!1:a=="null"?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?c.parseJSON(a):a:b):a}catch(d){return a}}function Z(a,b){b(a);for(var c in a.childNodes)Z(a.childNodes[c],b)}var a,b,c,d,e=[],f=e.slice,g=e.filter,h=window.document,i={},j={},k=h.defaultView.getComputedStyle,l={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},m=/^\s*<(\w+|!)[^>]*>/,n=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,o=/^(?:body|html)$/i,p=["val","css","html","text","data","width","height","offset"],q=["after","prepend","before","append"],r=h.createElement("table"),s=h.createElement("tr"),t={tr:h.createElement("tbody"),tbody:r,thead:r,tfoot:r,td:s,th:s,"*":h.createElement("div")},u=/complete|loaded|interactive/,v=/^\.([\w-]+)$/,w=/^#([\w-]*)$/,x=/^[\w-]+$/,y={},z=y.toString,A={},B,C,D=h.createElement("div");return A.matches=function(a,b){if(!a||a.nodeType!==1)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=D).appendChild(a),d=~A.qsa(e,b).indexOf(a),f&&D.removeChild(a),d},B=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},C=function(a){return g.call(a,function(b,c){return a.indexOf(b)==c})},A.fragment=function(b,d,e){b.replace&&(b=b.replace(n,"<$1>")),d===a&&(d=m.test(b)&&RegExp.$1),d in t||(d="*");var g,h,i=t[d];return i.innerHTML=""+b,h=c.each(f.call(i.childNodes),function(){i.removeChild(this)}),J(e)&&(g=c(h),c.each(e,function(a,b){p.indexOf(a)>-1?g[a](b):g.attr(a,b)})),h},A.Z=function(a,b){return a=a||[],a.__proto__=c.fn,a.selector=b||"",a},A.isZ=function(a){return a instanceof A.Z},A.init=function(b,d){if(!b)return A.Z();if(F(b))return c(h).ready(b);if(A.isZ(b))return b;var e;if(K(b))e=M(b);else if(I(b))e=[J(b)?c.extend({},b):b],b=null;else if(m.test(b))e=A.fragment(b.trim(),RegExp.$1,d),b=null;else{if(d!==a)return c(d).find(b);e=A.qsa(h,b)}return A.Z(e,b)},c=function(a,b){return A.init(a,b)},c.extend=function(a){var b,c=f.call(arguments,1);return typeof a=="boolean"&&(b=a,a=c.shift()),c.forEach(function(c){T(a,c,b)}),a},A.qsa=function(a,b){var c;return H(a)&&w.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:a.nodeType!==1&&a.nodeType!==9?[]:f.call(v.test(b)?a.getElementsByClassName(RegExp.$1):x.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},c.contains=function(a,b){return a!==b&&a.contains(b)},c.type=E,c.isFunction=F,c.isWindow=G,c.isArray=K,c.isPlainObject=J,c.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},c.inArray=function(a,b,c){return e.indexOf.call(b,a,c)},c.camelCase=B,c.trim=function(a){return a.trim()},c.uuid=0,c.support={},c.expr={},c.map=function(a,b){var c,d=[],e,f;if(L(a))for(e=0;e=0?b:b+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){this.parentNode!=null&&this.parentNode.removeChild(this)})},each:function(a){return e.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return F(a)?this.not(this.not(a)):c(g.call(this,function(b){return A.matches(b,a)}))},add:function(a,b){return c(C(this.concat(c(a,b))))},is:function(a){return this.length>0&&A.matches(this[0],a)},not:function(b){var d=[];if(F(b)&&b.call!==a)this.each(function(a){b.call(this,a)||d.push(this)});else{var e=typeof b=="string"?this.filter(b):L(b)&&F(b.item)?f.call(b):c(b);this.forEach(function(a){e.indexOf(a)<0&&d.push(a)})}return c(d)},has:function(a){return this.filter(function(){return I(a)?c.contains(this,a):c(this).find(a).size()})},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!I(a)?a:c(a)},last:function(){var a=this[this.length-1];return a&&!I(a)?a:c(a)},find:function(a){var b,d=this;return typeof a=="object"?b=c(a).filter(function(){var a=this;return e.some.call(d,function(b){return c.contains(b,a)})}):this.length==1?b=c(A.qsa(this[0],a)):b=this.map(function(){return A.qsa(this,a)}),b},closest:function(a,b){var d=this[0],e=!1;typeof a=="object"&&(e=c(a));while(d&&!(e?e.indexOf(d)>=0:A.matches(d,a)))d=d!==b&&!H(d)&&d.parentNode;return c(d)},parents:function(a){var b=[],d=this;while(d.length>0)d=c.map(d,function(a){if((a=a.parentNode)&&!H(a)&&b.indexOf(a)<0)return b.push(a),a});return U(b,a)},parent:function(a){return U(C(this.pluck("parentNode")),a)},children:function(a){return U(this.map(function(){return S(this)}),a)},contents:function(){return this.map(function(){return f.call(this.childNodes)})},siblings:function(a){return U(this.map(function(a,b){return g.call(S(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return c.map(this,function(b){return b[a]})},show:function(){return this.each(function(){this.style.display=="none"&&(this.style.display=null),k(this,"").getPropertyValue("display")=="none"&&(this.style.display=R(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var b=F(a);if(this[0]&&!b)var d=c(a).get(0),e=d.parentNode||this.length>1;return this.each(function(f){c(this).wrapAll(b?a.call(this,f):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){c(this[0]).before(a=c(a));var b;while((b=a.children()).length)a=b.first();c(a).append(this)}return this},wrapInner:function(a){var b=F(a);return this.each(function(d){var e=c(this),f=e.contents(),g=b?a.call(this,d):a;f.length?f.wrapAll(g):e.append(g)})},unwrap:function(){return this.parent().each(function(){c(this).replaceWith(c(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(b){return this.each(function(){var d=c(this);(b===a?d.css("display")=="none":b)?d.show():d.hide()})},prev:function(a){return c(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return c(this.pluck("nextElementSibling")).filter(a||"*")},html:function(b){return b===a?this.length>0?this[0].innerHTML:null:this.each(function(a){var d=this.innerHTML;c(this).empty().append(V(this,b,a,d))})},text:function(b){return b===a?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=b})},attr:function(c,d){var e;return typeof c=="string"&&d===a?this.length==0||this[0].nodeType!==1?a:c=="value"&&this[0].nodeName=="INPUT"?this.val():!(e=this[0].getAttribute(c))&&c in this[0]?this[0][c]:e:this.each(function(a){if(this.nodeType!==1)return;if(I(c))for(b in c)W(this,b,c[b]);else W(this,c,V(this,d,a,this.getAttribute(c)))})},removeAttr:function(a){return this.each(function(){this.nodeType===1&&W(this,a)})},prop:function(b,c){return c===a?this[0]&&this[0][b]:this.each(function(a){this[b]=V(this,c,a,this[b])})},data:function(b,c){var d=this.attr("data-"+O(b),c);return d!==null?Y(d):a},val:function(b){return b===a?this[0]&&(this[0].multiple?c(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(a){this.value=V(this,b,a,this.value)})},offset:function(a){if(a)return this.each(function(b){var d=c(this),e=V(this,a,b,d.offset()),f=d.offsetParent().offset(),g={top:e.top-f.top,left:e.left-f.left};d.css("position")=="static"&&(g.position="relative"),d.css(g)});if(this.length==0)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(a,c){if(arguments.length<2&&typeof a=="string")return this[0]&&(this[0].style[B(a)]||k(this[0],"").getPropertyValue(a));var d="";if(E(a)=="string")!c&&c!==0?this.each(function(){this.style.removeProperty(O(a))}):d=O(a)+":"+Q(a,c);else for(b in a)!a[b]&&a[b]!==0?this.each(function(){this.style.removeProperty(O(b))}):d+=O(b)+":"+Q(b,a[b])+";";return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(c(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return e.some.call(this,function(a){return this.test(X(a))},P(a))},addClass:function(a){return this.each(function(b){d=[];var e=X(this),f=V(this,a,b,e);f.split(/\s+/g).forEach(function(a){c(this).hasClass(a)||d.push(a)},this),d.length&&X(this,e+(e?" ":"")+d.join(" "))})},removeClass:function(b){return this.each(function(c){if(b===a)return X(this,"");d=X(this),V(this,b,c,d).split(/\s+/g).forEach(function(a){d=d.replace(P(a)," ")}),X(this,d.trim())})},toggleClass:function(b,d){return this.each(function(e){var f=c(this),g=V(this,b,e,X(this));g.split(/\s+/g).forEach(function(b){(d===a?!f.hasClass(b):d)?f.addClass(b):f.removeClass(b)})})},scrollTop:function(){if(!this.length)return;return"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY},position:function(){if(!this.length)return;var a=this[0],b=this.offsetParent(),d=this.offset(),e=o.test(b[0].nodeName)?{top:0,left:0}:b.offset();return d.top-=parseFloat(c(a).css("margin-top"))||0,d.left-=parseFloat(c(a).css("margin-left"))||0,e.top+=parseFloat(c(b[0]).css("border-top-width"))||0,e.left+=parseFloat(c(b[0]).css("border-left-width"))||0,{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||h.body;while(a&&!o.test(a.nodeName)&&c(a).css("position")=="static")a=a.offsetParent;return a})}},c.fn.detach=c.fn.remove,["width","height"].forEach(function(b){c.fn[b]=function(d){var e,f=this[0],g=b.replace(/./,function(a){return a[0].toUpperCase()});return d===a?G(f)?f["inner"+g]:H(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[b]:this.each(function(a){f=c(this),f.css(b,V(this,d,a,f[b]()))})}}),q.forEach(function(a,b){var d=b%2;c.fn[a]=function(){var a,e=c.map(arguments,function(b){return a=E(b),a=="object"||a=="array"||b==null?b:A.fragment(b)}),f,g=this.length>1;return e.length<1?this:this.each(function(a,h){f=d?h:h.parentNode,h=b==0?h.nextSibling:b==1?h.firstChild:b==2?h:null,e.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!f)return c(a).remove();Z(f.insertBefore(a,h),function(a){a.nodeName!=null&&a.nodeName.toUpperCase()==="SCRIPT"&&(!a.type||a.type==="text/javascript")&&!a.src&&window.eval.call(window,a.innerHTML)})})})},c.fn[d?a+"To":"insert"+(b?"Before":"After")]=function(b){return c(b)[a](this),this}}),A.Z.prototype=c.fn,A.uniq=C,A.deserializeValue=Y,c.zepto=A,c}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);if(c.webkit=!!d)c.version=d[1];e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function g(a){return a._zid||(a._zid=d++)}function h(a,b,d,e){b=i(b);if(b.ns)var f=j(b.ns);return(c[g(a)]||[]).filter(function(a){return a&&(!b.e||a.e==b.e)&&(!b.ns||f.test(a.ns))&&(!d||g(a.fn)===g(d))&&(!e||a.sel==e)})}function i(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function j(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function k(b,c,d){a.type(b)!="string"?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function l(a,b){return a.del&&(a.e=="focus"||a.e=="blur")||!!b}function m(a){return f[a]||a}function n(b,d,e,h,j,n){var o=g(b),p=c[o]||(c[o]=[]);k(d,e,function(c,d){var e=i(c);e.fn=d,e.sel=h,e.e in f&&(d=function(b){var c=b.relatedTarget;if(!c||c!==this&&!a.contains(this,c))return e.fn.apply(this,arguments)}),e.del=j&&j(d,c);var g=e.del||d;e.proxy=function(a){var c=g.apply(b,[a].concat(a.data));return c===!1&&(a.preventDefault(),a.stopPropagation()),c},e.i=p.length,p.push(e),b.addEventListener(m(e.e),e.proxy,l(e,n))})}function o(a,b,d,e,f){var i=g(a);k(b||"",d,function(b,d){h(a,b,d,e).forEach(function(b){delete c[i][b.i],a.removeEventListener(m(b.e),b.proxy,l(b,f))})})}function t(b){var c,d={originalEvent:b};for(c in b)!r.test(c)&&b[c]!==undefined&&(d[c]=b[c]);return a.each(s,function(a,c){d[a]=function(){return this[c]=p,b[a].apply(b,arguments)},d[c]=q}),d}function u(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var b=a.zepto.qsa,c={},d=1,e={},f={mouseenter:"mouseover",mouseleave:"mouseout"};e.click=e.mousedown=e.mouseup=e.mousemove="MouseEvents",a.event={add:n,remove:o},a.proxy=function(b,c){if(a.isFunction(b)){var d=function(){return b.apply(c,arguments)};return d._zid=g(b),d}if(typeof c=="string")return a.proxy(b[c],b);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){n(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){o(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){n(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return o(d,b,a),c}})})};var p=function(){return!0},q=function(){return!1},r=/^([A-Z]|layer[XY]$)/,s={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){n(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);if(g)return e=a.extend(t(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){o(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){if(typeof b=="string"||a.isPlainObject(b))b=a.Event(b);return u(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,c){var d,e;return this.each(function(f,g){d=t(typeof b=="string"?a.Event(b):b),d.data=c,d.target=g,a.each(h(g,b.type||b),function(a,b){e=b.proxy(d);if(d.isImmediatePropagationStopped())return!1})}),e},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){typeof a!="string"&&(b=a,a=b.type);var c=document.createEvent(e[a]||"Events"),d=!0;if(b)for(var f in b)f=="bubbles"?d=!!b[f]:c[f]=b[f];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function($){function triggerAndReturn(a,b,c){var d=$.Event(b);return $(a).trigger(d,c),!d.defaultPrevented}function triggerGlobal(a,b,c,d){if(a.global)return triggerAndReturn(b||document,c,d)}function ajaxStart(a){a.global&&$.active++===0&&triggerGlobal(a,null,"ajaxStart")}function ajaxStop(a){a.global&&!--$.active&&triggerGlobal(a,null,"ajaxStop")}function ajaxBeforeSend(a,b){var c=b.context;if(b.beforeSend.call(c,a,b)===!1||triggerGlobal(b,c,"ajaxBeforeSend",[a,b])===!1)return!1;triggerGlobal(b,c,"ajaxSend",[a,b])}function ajaxSuccess(a,b,c){var d=c.context,e="success";c.success.call(d,a,e,b),triggerGlobal(c,d,"ajaxSuccess",[b,c,a]),ajaxComplete(e,b,c)}function ajaxError(a,b,c,d){var e=d.context;d.error.call(e,c,b,a),triggerGlobal(d,e,"ajaxError",[c,d,a]),ajaxComplete(b,c,d)}function ajaxComplete(a,b,c){var d=c.context;c.complete.call(d,b,a),triggerGlobal(c,d,"ajaxComplete",[b,c]),ajaxStop(c)}function empty(){}function mimeToDataType(a){return a&&(a=a.split(";",2)[0]),a&&(a==htmlType?"html":a==jsonType?"json":scriptTypeRE.test(a)?"script":xmlTypeRE.test(a)&&"xml")||"text"}function appendQuery(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function serializeData(a){a.processData&&a.data&&$.type(a.data)!="string"&&(a.data=$.param(a.data,a.traditional)),a.data&&(!a.type||a.type.toUpperCase()=="GET")&&(a.url=appendQuery(a.url,a.data))}function parseArguments(a,b,c,d){var e=!$.isFunction(b);return{url:a,data:e?b:undefined,success:e?$.isFunction(c)?c:undefined:b,dataType:e?d||c:c}}function serialize(a,b,c,d){var e,f=$.isArray(b);$.each(b,function(b,g){e=$.type(g),d&&(b=c?d:d+"["+(f?"":b)+"]"),!d&&f?a.add(g.name,g.value):e=="array"||!c&&e=="object"?serialize(a,g,c,b):a.add(b,g)})}var jsonpID=0,document=window.document,key,name,rscript=/)<[^<]*)*<\/script>/gi,scriptTypeRE=/^(?:text|application)\/javascript/i,xmlTypeRE=/^(?:text|application)\/xml/i,jsonType="application/json",htmlType="text/html",blankRE=/^\s*$/;$.active=0,$.ajaxJSONP=function(a){if("type"in a){var b="jsonp"+ ++jsonpID,c=document.createElement("script"),d=function(){clearTimeout(g),$(c).remove(),delete window[b]},e=function(c){d();if(!c||c=="timeout")window[b]=empty;ajaxError(null,c||"abort",f,a)},f={abort:e},g;return ajaxBeforeSend(f,a)===!1?(e("abort"),!1):(window[b]=function(b){d(),ajaxSuccess(b,f,a)},c.onerror=function(){e("error")},c.src=a.url.replace(/=\?/,"="+b),$("head").append(c),a.timeout>0&&(g=setTimeout(function(){e("timeout")},a.timeout)),f)}return $.ajax(a)},$.ajaxSettings={type:"GET",beforeSend:empty,success:empty,error:empty,complete:empty,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript",json:jsonType,xml:"application/xml, text/xml",html:htmlType,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},$.ajax=function(options){var settings=$.extend({},options||{});for(key in $.ajaxSettings)settings[key]===undefined&&(settings[key]=$.ajaxSettings[key]);ajaxStart(settings),settings.crossDomain||(settings.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(settings.url)&&RegExp.$2!=window.location.host),settings.url||(settings.url=window.location.toString()),serializeData(settings),settings.cache===!1&&(settings.url=appendQuery(settings.url,"_="+Date.now()));var dataType=settings.dataType,hasPlaceholder=/=\?/.test(settings.url);if(dataType=="jsonp"||hasPlaceholder)return hasPlaceholder||(settings.url=appendQuery(settings.url,"callback=?")),$.ajaxJSONP(settings);var mime=settings.accepts[dataType],baseHeaders={},protocol=/^([\w-]+:)\/\//.test(settings.url)?RegExp.$1:window.location.protocol,xhr=settings.xhr(),abortTimeout;settings.crossDomain||(baseHeaders["X-Requested-With"]="XMLHttpRequest"),mime&&(baseHeaders.Accept=mime,mime.indexOf(",")>-1&&(mime=mime.split(",",2)[0]),xhr.overrideMimeType&&xhr.overrideMimeType(mime));if(settings.contentType||settings.contentType!==!1&&settings.data&&settings.type.toUpperCase()!="GET")baseHeaders["Content-Type"]=settings.contentType||"application/x-www-form-urlencoded";settings.headers=$.extend(baseHeaders,settings.headers||{}),xhr.onreadystatechange=function(){if(xhr.readyState==4){xhr.onreadystatechange=empty,clearTimeout(abortTimeout);var result,error=!1;if(xhr.status>=200&&xhr.status<300||xhr.status==304||xhr.status==0&&protocol=="file:"){dataType=dataType||mimeToDataType(xhr.getResponseHeader("content-type")),result=xhr.responseText;try{dataType=="script"?(1,eval)(result):dataType=="xml"?result=xhr.responseXML:dataType=="json"&&(result=blankRE.test(result)?null:$.parseJSON(result))}catch(e){error=e}error?ajaxError(error,"parsererror",xhr,settings):ajaxSuccess(result,xhr,settings)}else ajaxError(null,xhr.status?"error":"abort",xhr,settings)}};var async="async"in settings?settings.async:!0;xhr.open(settings.type,settings.url,async);for(name in settings.headers)xhr.setRequestHeader(name,settings.headers[name]);return ajaxBeforeSend(xhr,settings)===!1?(xhr.abort(),!1):(settings.timeout>0&&(abortTimeout=setTimeout(function(){xhr.onreadystatechange=empty,xhr.abort(),ajaxError(null,"timeout",xhr,settings)},settings.timeout)),xhr.send(settings.data?settings.data:null),xhr)},$.get=function(a,b,c,d){return $.ajax(parseArguments.apply(null,arguments))},$.post=function(a,b,c,d){var e=parseArguments.apply(null,arguments);return e.type="POST",$.ajax(e)},$.getJSON=function(a,b,c){var d=parseArguments.apply(null,arguments);return d.dataType="json",$.ajax(d)},$.fn.load=function(a,b,c){if(!this.length)return this;var d=this,e=a.split(/\s/),f,g=parseArguments(a,b,c),h=g.success;return e.length>1&&(g.url=e[0],f=e[1]),g.success=function(a){d.html(f?$("
").html(a.replace(rscript,"")).find(f):a),h&&h.apply(d,arguments)},$.ajax(g),this};var escape=encodeURIComponent;$.param=function(a,b){var c=[];return c.add=function(a,b){this.push(escape(a)+"="+escape(b))},serialize(c,a,b),c.join("&").replace(/%20/g,"+")}}(Zepto),function(a){a.fn.serializeArray=function(){var b=[],c;return a(Array.prototype.slice.call(this.get(0).elements)).each(function(){c=a(this);var d=c.attr("type");this.nodeName.toLowerCase()!="fieldset"&&!this.disabled&&d!="submit"&&d!="reset"&&d!="button"&&(d!="radio"&&d!="checkbox"||this.checked)&&b.push({name:c.attr("name"),value:c.val()})}),b},a.fn.serialize=function(){var a=[];return this.serializeArray().forEach(function(b){a.push(encodeURIComponent(b.name)+"="+encodeURIComponent(b.value))}),a.join("&")},a.fn.submit=function(b){if(b)this.bind("submit",b);else if(this.length){var c=a.Event("submit");this.eq(0).trigger(c),c.defaultPrevented||this.get(0).submit()}return this}}(Zepto),function(a,b){function s(a){return t(a.replace(/([a-z])([A-Z])/,"$1-$2"))}function t(a){return a.toLowerCase()}function u(a){return d?d+a:t(a)}var c="",d,e,f,g={Webkit:"webkit",Moz:"",O:"o",ms:"MS"},h=window.document,i=h.createElement("div"),j=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,k,l,m,n,o,p,q,r={};a.each(g,function(a,e){if(i.style[a+"TransitionProperty"]!==b)return c="-"+t(a)+"-",d=e,!1}),k=c+"transform",r[l=c+"transition-property"]=r[m=c+"transition-duration"]=r[n=c+"transition-timing-function"]=r[o=c+"animation-name"]=r[p=c+"animation-duration"]=r[q=c+"animation-timing-function"]="",a.fx={off:d===b&&i.style.transitionProperty===b,speeds:{_default:400,fast:200,slow:600},cssPrefix:c,transitionEnd:u("TransitionEnd"),animationEnd:u("AnimationEnd")},a.fn.animate=function(b,c,d,e){return a.isPlainObject(c)&&(d=c.easing,e=c.complete,c=c.duration),c&&(c=(typeof c=="number"?c:a.fx.speeds[c]||a.fx.speeds._default)/1e3),this.anim(b,c,d,e)},a.fn.anim=function(c,d,e,f){var g,h={},i,t="",u=this,v,w=a.fx.transitionEnd;d===b&&(d=.4),a.fx.off&&(d=0);if(typeof c=="string")h[o]=c,h[p]=d+"s",h[q]=e||"linear",w=a.fx.animationEnd;else{i=[];for(g in c)j.test(g)?t+=g+"("+c[g]+") ":(h[g]=c[g],i.push(s(g)));t&&(h[k]=t,i.push(k)),d>0&&typeof c=="object"&&(h[l]=i.join(", "),h[m]=d+"s",h[n]=e||"linear")}return v=function(b){if(typeof b!="undefined"){if(b.target!==b.currentTarget)return;a(b.target).unbind(w,v)}a(this).css(r),f&&f.call(this)},d>0&&this.bind(w,v),this.size()&&this.get(0).clientLeft,this.css(h),d<=0&&setTimeout(function(){u.each(function(){v.call(this)})},0),this},i=null}(Zepto) \ No newline at end of file +(function(a){String.prototype.trim===a&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.reduce===a&&(Array.prototype.reduce=function(b){if(this===void 0||this===null)throw new TypeError;var c=Object(this),d=c.length>>>0,e=0,f;if(typeof b!="function")throw new TypeError;if(d==0&&arguments.length==1)throw new TypeError;if(arguments.length>=2)f=arguments[1];else do{if(e in c){f=c[e++];break}if(++e>=d)throw new TypeError}while(!0);while(e0?c.fn.concat.apply([],a):a}function O(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function P(a){return a in j?j[a]:j[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function Q(a,b){return typeof b=="number"&&!l[O(a)]?b+"px":b}function R(a){var b,c;return i[a]||(b=h.createElement(a),h.body.appendChild(b),c=k(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),c=="none"&&(c="block"),i[a]=c),i[a]}function S(a){return"children"in a?f.call(a.children):c.map(a.childNodes,function(a){if(a.nodeType==1)return a})}function T(c,d,e){for(b in d)e&&(J(d[b])||K(d[b]))?(J(d[b])&&!J(c[b])&&(c[b]={}),K(d[b])&&!K(c[b])&&(c[b]=[]),T(c[b],d[b],e)):d[b]!==a&&(c[b]=d[b])}function U(b,d){return d===a?c(b):c(b).filter(d)}function V(a,b,c,d){return F(b)?b.call(a,c,d):b}function W(a,b,c){c==null?a.removeAttribute(b):a.setAttribute(b,c)}function X(b,c){var d=b.className,e=d&&d.baseVal!==a;if(c===a)return e?d.baseVal:d;e?d.baseVal=c:b.className=c}function Y(a){var b;try{return a?a=="true"||(a=="false"?!1:a=="null"?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?c.parseJSON(a):a:b):a}catch(d){return a}}function Z(a,b){b(a);for(var c in a.childNodes)Z(a.childNodes[c],b)}var a,b,c,d,e=[],f=e.slice,g=e.filter,h=window.document,i={},j={},k=h.defaultView.getComputedStyle,l={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},m=/^\s*<(\w+|!)[^>]*>/,n=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,o=/^(?:body|html)$/i,p=["val","css","html","text","data","width","height","offset"],q=["after","prepend","before","append"],r=h.createElement("table"),s=h.createElement("tr"),t={tr:h.createElement("tbody"),tbody:r,thead:r,tfoot:r,td:s,th:s,"*":h.createElement("div")},u=/complete|loaded|interactive/,v=/^\.([\w-]+)$/,w=/^#([\w-]*)$/,x=/^[\w-]+$/,y={},z=y.toString,A={},B,C,D=h.createElement("div");return A.matches=function(a,b){if(!a||a.nodeType!==1)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=D).appendChild(a),d=~A.qsa(e,b).indexOf(a),f&&D.removeChild(a),d},B=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},C=function(a){return g.call(a,function(b,c){return a.indexOf(b)==c})},A.fragment=function(b,d,e){b.replace&&(b=b.replace(n,"<$1>")),d===a&&(d=m.test(b)&&RegExp.$1),d in t||(d="*");var g,h,i=t[d];return i.innerHTML=""+b,h=c.each(f.call(i.childNodes),function(){i.removeChild(this)}),J(e)&&(g=c(h),c.each(e,function(a,b){p.indexOf(a)>-1?g[a](b):g.attr(a,b)})),h},A.Z=function(a,b){return a=a||[],a.__proto__=c.fn,a.selector=b||"",a},A.isZ=function(a){return a instanceof A.Z},A.init=function(b,d){if(!b)return A.Z();if(F(b))return c(h).ready(b);if(A.isZ(b))return b;var e;if(K(b))e=M(b);else if(I(b))e=[J(b)?c.extend({},b):b],b=null;else if(m.test(b))e=A.fragment(b.trim(),RegExp.$1,d),b=null;else{if(d!==a)return c(d).find(b);e=A.qsa(h,b)}return A.Z(e,b)},c=function(a,b){return A.init(b, a)},c.extend=function(a){var b,c=f.call(arguments,1);return typeof a=="boolean"&&(b=a,a=c.shift()),c.forEach(function(c){T(a,c,b)}),a},A.qsa=function(a, b){var c;return H(a)&&w.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:a.nodeType!==1&&a.nodeType!==9?[]:f.call(v.test(b)?a.getElementsByClassName(RegExp.$1):x.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},c.contains=function(a, b){return a!==b&&a.contains(b)},c.type=E,c.isFunction=F,c.isWindow=G,c.isArray=K,c.isPlainObject=J,c.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},c.inArray=function(a,b,c){return e.indexOf.call(b,a,c)},c.camelCase=B,c.trim=function(a){return a.trim()},c.uuid=0,c.support={},c.expr={},c.map=function(a,b){var c,d=[],e,f;if(L(a))for(e=0;e=0?b:b+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){this.parentNode!=null&&this.parentNode.removeChild(this)})},each:function(a){return e.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return F(a)?this.not(this.not(a)):c(g.call(this,function(b){return A.matches(b,a)}))},add:function(a,b){return c(C(this.concat(c(a,b))))},is:function(a){return this.length>0&&A.matches(this[0],a)},not:function(b){var d=[];if(F(b)&&b.call!==a)this.each(function(a){b.call(this,a)||d.push(this)});else{var e=typeof b=="string"?this.filter(b):L(b)&&F(b.item)?f.call(b):c(b);this.forEach(function(a){e.indexOf(a)<0&&d.push(a)})}return c(d)},has:function(a){return this.filter(function(){return I(a)?c.contains(this,a):c(this).find(a).size()})},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!I(a)?a:c(a)},last:function(){var a=this[this.length-1];return a&&!I(a)?a:c(a)},find:function(a){var b,d=this;return typeof a=="object"?b=c(a).filter(function(){var a=this;return e.some.call(d,function(b){return c.contains(b,a)})}):this.length==1?b=c(A.qsa(this[0],a)):b=this.map(function(){return A.qsa(this,a)}),b},closest:function(a,b){var d=this[0],e=!1;typeof a=="object"&&(e=c(a));while(d&&!(e?e.indexOf(d)>=0:A.matches(d,a)))d=d!==b&&!H(d)&&d.parentNode;return c(d)},parents:function(a){var b=[],d=this;while(d.length>0)d=c.map(d,function(a){if((a=a.parentNode)&&!H(a)&&b.indexOf(a)<0)return b.push(a),a});return U(b,a)},parent:function(a){return U(C(this.pluck("parentNode")),a)},children:function(a){return U(this.map(function(){return S(this)}),a)},contents:function(){return this.map(function(){return f.call(this.childNodes)})},siblings:function(a){return U(this.map(function(a,b){return g.call(S(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return c.map(this,function(b){return b[a]})},show:function(){return this.each(function(){this.style.display=="none"&&(this.style.display=null),k(this,"").getPropertyValue("display")=="none"&&(this.style.display=R(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var b=F(a);if(this[0]&&!b)var d=c(a).get(0),e=d.parentNode||this.length>1;return this.each(function(f){c(this).wrapAll(b?a.call(this,f):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){c(this[0]).before(a=c(a));var b;while((b=a.children()).length)a=b.first();c(a).append(this)}return this},wrapInner:function(a){var b=F(a);return this.each(function(d){var e=c(this),f=e.contents(),g=b?a.call(this,d):a;f.length?f.wrapAll(g):e.append(g)})},unwrap:function(){return this.parent().each(function(){c(this).replaceWith(c(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(b){return this.each(function(){var d=c(this);(b===a?d.css("display")=="none":b)?d.show():d.hide()})},prev:function(a){return c(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return c(this.pluck("nextElementSibling")).filter(a||"*")},html:function(b){return b===a?this.length>0?this[0].innerHTML:null:this.each(function(a){var d=this.innerHTML;c(this).empty().append(V(this,b,a,d))})},text:function(b){return b===a?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=b})},attr:function(c,d){var e;return typeof c=="string"&&d===a?this.length==0||this[0].nodeType!==1?a:c=="value"&&this[0].nodeName=="INPUT"?this.val():!(e=this[0].getAttribute(c))&&c in this[0]?this[0][c]:e:this.each(function(a){if(this.nodeType!==1)return;if(I(c))for(b in c)W(this,b,c[b]);else W(this,c,V(this,d,a,this.getAttribute(c)))})},removeAttr:function(a){return this.each(function(){this.nodeType===1&&W(this,a)})},prop:function(b,c){return c===a?this[0]&&this[0][b]:this.each(function(a){this[b]=V(this,c,a,this[b])})},data:function(b,c){var d=this.attr("data-"+O(b),c);return d!==null?Y(d):a},val:function(b){return b===a?this[0]&&(this[0].multiple?c(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(a){this.value=V(this,b,a,this.value)})},offset:function(a){if(a)return this.each(function(b){var d=c(this),e=V(this,a,b,d.offset()),f=d.offsetParent().offset(),g={top:e.top-f.top,left:e.left-f.left};d.css("position")=="static"&&(g.position="relative"),d.css(g)});if(this.length==0)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(a,c){if(arguments.length<2&&typeof a=="string")return this[0]&&(this[0].style[B(a)]||k(this[0],"").getPropertyValue(a));var d="";if(E(a)=="string")!c&&c!==0?this.each(function(){this.style.removeProperty(O(a))}):d=O(a)+":"+Q(a,c);else for(b in a)!a[b]&&a[b]!==0?this.each(function(){this.style.removeProperty(O(b))}):d+=O(b)+":"+Q(b,a[b])+";";return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(c(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return e.some.call(this,function(a){return this.test(X(a))},P(a))},addClass:function(a){return this.each(function(b){d=[];var e=X(this),f=V(this,a,b,e);f.split(/\s+/g).forEach(function(a){c(this).hasClass(a)||d.push(a)},this),d.length&&X(this,e+(e?" ":"")+d.join(" "))})},removeClass:function(b){return this.each(function(c){if(b===a)return X(this,"");d=X(this),V(this,b,c,d).split(/\s+/g).forEach(function(a){d=d.replace(P(a)," ")}),X(this,d.trim())})},toggleClass:function(b,d){return this.each(function(e){var f=c(this),g=V(this,b,e,X(this));g.split(/\s+/g).forEach(function(b){(d===a?!f.hasClass(b):d)?f.addClass(b):f.removeClass(b)})})},scrollTop:function(){if(!this.length)return;return"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY},position:function(){if(!this.length)return;var a=this[0],b=this.offsetParent(),d=this.offset(),e=o.test(b[0].nodeName)?{top:0,left:0}:b.offset();return d.top-=parseFloat(c(a).css("margin-top"))||0,d.left-=parseFloat(c(a).css("margin-left"))||0,e.top+=parseFloat(c(b[0]).css("border-top-width"))||0,e.left+=parseFloat(c(b[0]).css("border-left-width"))||0,{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||h.body;while(a&&!o.test(a.nodeName)&&c(a).css("position")=="static")a=a.offsetParent;return a})}},c.fn.detach=c.fn.remove,["width","height"].forEach(function(b){c.fn[b]=function(d){var e,f=this[0],g=b.replace(/./,function(a){return a[0].toUpperCase()});return d===a?G(f)?f["inner"+g]:H(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[b]:this.each(function(a){f=c(this),f.css(b,V(this,d,a,f[b]()))})}}),q.forEach(function(a,b){var d=b%2;c.fn[a]=function(){var a,e=c.map(arguments,function(b){return a=E(b),a=="object"||a=="array"||b==null?b:A.fragment(b)}),f,g=this.length>1;return e.length<1?this:this.each(function(a,h){f=d?h:h.parentNode,h=b==0?h.nextSibling:b==1?h.firstChild:b==2?h:null,e.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!f)return c(a).remove();Z(f.insertBefore(a,h),function(a){a.nodeName!=null&&a.nodeName.toUpperCase()==="SCRIPT"&&(!a.type||a.type==="text/javascript")&&!a.src&&window.eval.call(window,a.innerHTML)})})})},c.fn[d?a+"To":"insert"+(b?"Before":"After")]=function(b){return c(b)[a](this),this}}),A.Z.prototype=c.fn,A.uniq=C,A.deserializeValue=Y,c.zepto=A,c}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);if(c.webkit=!!d)c.version=d[1];e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function g(a){return a._zid||(a._zid=d++)}function h(a,b,d,e){b=i(b);if(b.ns)var f=j(b.ns);return(c[g(a)]||[]).filter(function(a){return a&&(!b.e||a.e==b.e)&&(!b.ns||f.test(a.ns))&&(!d||g(a.fn)===g(d))&&(!e||a.sel==e)})}function i(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function j(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function k(b,c,d){a.type(b)!="string"?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function l(a,b){return a.del&&(a.e=="focus"||a.e=="blur")||!!b}function m(a){return f[a]||a}function n(b,d,e,h,j,n){var o=g(b),p=c[o]||(c[o]=[]);k(d,e,function(c,d){var e=i(c);e.fn=d,e.sel=h,e.e in f&&(d=function(b){var c=b.relatedTarget;if(!c||c!==this&&!a.contains(this,c))return e.fn.apply(this,arguments)}),e.del=j&&j(d,c);var g=e.del||d;e.proxy=function(a){var c=g.apply(b,[a].concat(a.data));return c===!1&&(a.preventDefault(),a.stopPropagation()),c},e.i=p.length,p.push(e),b.addEventListener(m(e.e),e.proxy,l(e,n))})}function o(a,b,d,e,f){var i=g(a);k(b||"",d,function(b,d){h(a,b,d,e).forEach(function(b){delete c[i][b.i],a.removeEventListener(m(b.e),b.proxy,l(b,f))})})}function t(b){var c,d={originalEvent:b};for(c in b)!r.test(c)&&b[c]!==undefined&&(d[c]=b[c]);return a.each(s,function(a,c){d[a]=function(){return this[c]=p,b[a].apply(b,arguments)},d[c]=q}),d}function u(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var b=a.zepto.qsa,c={},d=1,e={},f={mouseenter:"mouseover",mouseleave:"mouseout"};e.click=e.mousedown=e.mouseup=e.mousemove="MouseEvents",a.event={add:n,remove:o},a.proxy=function(b,c){if(a.isFunction(b)){var d=function(){return b.apply(c,arguments)};return d._zid=g(b),d}if(typeof c=="string")return a.proxy(b[c],b);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){n(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){o(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){n(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return o(d,b,a),c}})})};var p=function(){return!0},q=function(){return!1},r=/^([A-Z]|layer[XY]$)/,s={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){n(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);if(g)return e=a.extend(t(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){o(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){if(typeof b=="string"||a.isPlainObject(b))b=a.Event(b);return u(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,c){var d,e;return this.each(function(f,g){d=t(typeof b=="string"?a.Event(b):b),d.data=c,d.target=g,a.each(h(g,b.type||b),function(a,b){e=b.proxy(d);if(d.isImmediatePropagationStopped())return!1})}),e},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){typeof a!="string"&&(b=a,a=b.type);var c=document.createEvent(e[a]||"Events"),d=!0;if(b)for(var f in b)f=="bubbles"?d=!!b[f]:c[f]=b[f];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function($){function triggerAndReturn(a,b,c){var d=$.Event(b);return $(a).trigger(d,c),!d.defaultPrevented}function triggerGlobal(a,b,c,d){if(a.global)return triggerAndReturn(b||document,c,d)}function ajaxStart(a){a.global&&$.active++===0&&triggerGlobal(a,null,"ajaxStart")}function ajaxStop(a){a.global&&!--$.active&&triggerGlobal(a,null,"ajaxStop")}function ajaxBeforeSend(a,b){var c=b.context;if(b.beforeSend.call(c,a,b)===!1||triggerGlobal(b,c,"ajaxBeforeSend",[a,b])===!1)return!1;triggerGlobal(b,c,"ajaxSend",[a,b])}function ajaxSuccess(a,b,c){var d=c.context,e="success";c.success.call(d,a,e,b),triggerGlobal(c,d,"ajaxSuccess",[b,c,a]),ajaxComplete(e,b,c)}function ajaxError(a,b,c,d){var e=d.context;d.error.call(e,c,b,a),triggerGlobal(d,e,"ajaxError",[c,d,a]),ajaxComplete(b,c,d)}function ajaxComplete(a,b,c){var d=c.context;c.complete.call(d,b,a),triggerGlobal(c,d,"ajaxComplete",[b,c]),ajaxStop(c)}function empty(){}function mimeToDataType(a){return a&&(a=a.split(";",2)[0]),a&&(a==htmlType?"html":a==jsonType?"json":scriptTypeRE.test(a)?"script":xmlTypeRE.test(a)&&"xml")||"text"}function appendQuery(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function serializeData(a){a.processData&&a.data&&$.type(a.data)!="string"&&(a.data=$.param(a.data,a.traditional)),a.data&&(!a.type||a.type.toUpperCase()=="GET")&&(a.url=appendQuery(a.url,a.data))}function parseArguments(a,b,c,d){var e=!$.isFunction(b);return{url:a,data:e?b:undefined,success:e?$.isFunction(c)?c:undefined:b,dataType:e?d||c:c}}function serialize(a,b,c,d){var e,f=$.isArray(b);$.each(b,function(b,g){e=$.type(g),d&&(b=c?d:d+"["+(f?"":b)+"]"),!d&&f?a.add(g.name,g.value):e=="array"||!c&&e=="object"?serialize(a,g,c,b):a.add(b,g)})}var jsonpID=0,document=window.document,key,name,rscript=/)<[^<]*)*<\/script>/gi,scriptTypeRE=/^(?:text|application)\/javascript/i,xmlTypeRE=/^(?:text|application)\/xml/i,jsonType="application/json",htmlType="text/html",blankRE=/^\s*$/;$.active=0,$.ajaxJSONP=function(a){if("type"in a){var b="jsonp"+ ++jsonpID,c=document.createElement("script"),d=function(){clearTimeout(g),$(c).remove(),delete window[b]},e=function(c){d();if(!c||c=="timeout")window[b]=empty;ajaxError(null,c||"abort",f,a)},f={abort:e},g;return ajaxBeforeSend(f,a)===!1?(e("abort"),!1):(window[b]=function(b){d(),ajaxSuccess(b,f,a)},c.onerror=function(){e("error")},c.src=a.url.replace(/=\?/,"="+b),$("head").append(c),a.timeout>0&&(g=setTimeout(function(){e("timeout")},a.timeout)),f)}return $.ajax(a)},$.ajaxSettings={type:"GET",beforeSend:empty,success:empty,error:empty,complete:empty,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript",json:jsonType,xml:"application/xml, text/xml",html:htmlType,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},$.ajax=function(options){var settings=$.extend({},options||{});for(key in $.ajaxSettings)settings[key]===undefined&&(settings[key]=$.ajaxSettings[key]);ajaxStart(settings),settings.crossDomain||(settings.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(settings.url)&&RegExp.$2!=window.location.host),settings.url||(settings.url=window.location.toString()),serializeData(settings),settings.cache===!1&&(settings.url=appendQuery(settings.url,"_="+Date.now()));var dataType=settings.dataType,hasPlaceholder=/=\?/.test(settings.url);if(dataType=="jsonp"||hasPlaceholder)return hasPlaceholder||(settings.url=appendQuery(settings.url,"callback=?")),$.ajaxJSONP(settings);var mime=settings.accepts[dataType],baseHeaders={},protocol=/^([\w-]+:)\/\//.test(settings.url)?RegExp.$1:window.location.protocol,xhr=settings.xhr(),abortTimeout;settings.crossDomain||(baseHeaders["X-Requested-With"]="XMLHttpRequest"),mime&&(baseHeaders.Accept=mime,mime.indexOf(",")>-1&&(mime=mime.split(",",2)[0]),xhr.overrideMimeType&&xhr.overrideMimeType(mime));if(settings.contentType||settings.contentType!==!1&&settings.data&&settings.type.toUpperCase()!="GET")baseHeaders["Content-Type"]=settings.contentType||"application/x-www-form-urlencoded";settings.headers=$.extend(baseHeaders,settings.headers||{}),xhr.onreadystatechange=function(){if(xhr.readyState==4){xhr.onreadystatechange=empty,clearTimeout(abortTimeout);var result,error=!1;if(xhr.status>=200&&xhr.status<300||xhr.status==304||xhr.status==0&&protocol=="file:"){dataType=dataType||mimeToDataType(xhr.getResponseHeader("content-type")),result=xhr.responseText;try{dataType=="script"?(1,eval)(result):dataType=="xml"?result=xhr.responseXML:dataType=="json"&&(result=blankRE.test(result)?null:$.parseJSON(result))}catch(e){error=e}error?ajaxError(error,"parsererror",xhr,settings):ajaxSuccess(result,xhr,settings)}else ajaxError(null,xhr.status?"error":"abort",xhr,settings)}};var async="async"in settings?settings.async:!0;xhr.open(settings.type,settings.url,async);for(name in settings.headers)xhr.setRequestHeader(name,settings.headers[name]);return ajaxBeforeSend(xhr,settings)===!1?(xhr.abort(),!1):(settings.timeout>0&&(abortTimeout=setTimeout(function(){xhr.onreadystatechange=empty,xhr.abort(),ajaxError(null,"timeout",xhr,settings)},settings.timeout)),xhr.send(settings.data?settings.data:null),xhr)},$.get=function(a,b,c,d){return $.ajax(parseArguments.apply(null,arguments))},$.post=function(a,b,c,d){var e=parseArguments.apply(null,arguments);return e.type="POST",$.ajax(e)},$.getJSON=function(a,b,c){var d=parseArguments.apply(null,arguments);return d.dataType="json",$.ajax(d)},$.fn.load=function(a,b,c){if(!this.length)return this;var d=this,e=a.split(/\s/),f,g=parseArguments(a,b,c),h=g.success;return e.length>1&&(g.url=e[0],f=e[1]),g.success=function(a){d.html(f?$("
").html(a.replace(rscript,"")).find(f):a),h&&h.apply(d,arguments)},$.ajax(g),this};var escape=encodeURIComponent;$.param=function(a,b){var c=[];return c.add=function(a,b){this.push(escape(a)+"="+escape(b))},serialize(c,a,b),c.join("&").replace(/%20/g,"+")}}(Zepto),function(a){a.fn.serializeArray=function(){var b=[],c;return a(Array.prototype.slice.call(this.get(0).elements)).each(function(){c=a(this);var d=c.attr("type");this.nodeName.toLowerCase()!="fieldset"&&!this.disabled&&d!="submit"&&d!="reset"&&d!="button"&&(d!="radio"&&d!="checkbox"||this.checked)&&b.push({name:c.attr("name"),value:c.val()})}),b},a.fn.serialize=function(){var a=[];return this.serializeArray().forEach(function(b){a.push(encodeURIComponent(b.name)+"="+encodeURIComponent(b.value))}),a.join("&")},a.fn.submit=function(b){if(b)this.bind("submit",b);else if(this.length){var c=a.Event("submit");this.eq(0).trigger(c),c.defaultPrevented||this.get(0).submit()}return this}}(Zepto),function(a,b){function s(a){return t(a.replace(/([a-z])([A-Z])/,"$1-$2"))}function t(a){return a.toLowerCase()}function u(a){return d?d+a:t(a)}var c="",d,e,f,g={Webkit:"webkit",Moz:"",O:"o",ms:"MS"},h=window.document,i=h.createElement("div"),j=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,k,l,m,n,o,p,q,r={};a.each(g,function(a,e){if(i.style[a+"TransitionProperty"]!==b)return c="-"+t(a)+"-",d=e,!1}),k=c+"transform",r[l=c+"transition-property"]=r[m=c+"transition-duration"]=r[n=c+"transition-timing-function"]=r[o=c+"animation-name"]=r[p=c+"animation-duration"]=r[q=c+"animation-timing-function"]="",a.fx={off:d===b&&i.style.transitionProperty===b,speeds:{_default:400,fast:200,slow:600},cssPrefix:c,transitionEnd:u("TransitionEnd"),animationEnd:u("AnimationEnd")},a.fn.animate=function(b,c,d,e){return a.isPlainObject(c)&&(d=c.easing,e=c.complete,c=c.duration),c&&(c=(typeof c=="number"?c:a.fx.speeds[c]||a.fx.speeds._default)/1e3),this.anim(b,c,d,e)},a.fn.anim=function(c,d,e,f){var g,h={},i,t="",u=this,v,w=a.fx.transitionEnd;d===b&&(d=.4),a.fx.off&&(d=0);if(typeof c=="string")h[o]=c,h[p]=d+"s",h[q]=e||"linear",w=a.fx.animationEnd;else{i=[];for(g in c)j.test(g)?t+=g+"("+c[g]+") ":(h[g]=c[g],i.push(s(g)));t&&(h[k]=t,i.push(k)),d>0&&typeof c=="object"&&(h[l]=i.join(", "),h[m]=d+"s",h[n]=e||"linear")}return v=function(b){if(typeof b!="undefined"){if(b.target!==b.currentTarget)return;a(b.target).unbind(w,v)}a(this).css(r),f&&f.call(this)},d>0&&this.bind(w,v),this.size()&&this.get(0).clientLeft,this.css(h),d<=0&&setTimeout(function(){u.each(function(){v.call(this)})},0),this},i=null}(Zepto) \ No newline at end of file diff --git a/test/lib/gpio.js b/test/lib/gpio.js index b692df1..05957b2 100644 --- a/test/lib/gpio.js +++ b/test/lib/gpio.js @@ -16,54 +16,54 @@ describe('gpio', function () { describe('updatePinStates', function () { it('should update all pin states', function (done) { gpioProbe.initPinsWith(1); - gpio.updatePinStates(function (result) { + gpio.updatePinStates(config, function (result) { assert.deepEqual( result, [{ name: 'a', pin: 47, state: 1 }, { name: 'b', pin: 11, state: 1 }], 'states are not updated properly'); done(); - }, config); + }); }); }); describe('togglePin', function () { it('should change active pin to inactive', function (done) { gpioProbe.initPinsWith(1); - gpio.togglePin(function (res) { + gpio.togglePin(47, function (res) { assert.deepEqual( res, { pin: 47, state: 0 }, 'pin has not changed its state'); done(); - }, 47); + }); }); it('should change inactive pin to active', function (done) { gpioProbe.initPinsWith(0); - gpio.togglePin(function (res) { + gpio.togglePin(47, function (res) { assert.deepEqual( res, { pin: 47, state: 1 }, 'pin has not changed its state'); done(); - }, 47); + }); }); }); describe('setPin', function () { it('should set a pin by name to the given value', function (done) { gpioProbe.initPinsWith(0); - gpio.setPin(function () { + gpio.setPin('a', 1, function () { assert.equal(gpioProbe.emulatedPins[47], 1); - gpio.setPin(function () { + gpio.setPin('a', 1, function () { assert.equal(gpioProbe.emulatedPins[47], 1); - gpio.setPin(function () { + gpio.setPin('a', 0, function () { assert.equal(gpioProbe.emulatedPins[47], 0); done(); - }, 'a', 0); - }, 'a', 1); - }, 'a', 1); + }); + }); + }); }); }); }); From ed1f7d977fc3793d5df3461fc67f3cf3c0b135bc Mon Sep 17 00:00:00 2001 From: OvisMaximus Date: Sat, 6 Jan 2018 22:19:19 +0100 Subject: [PATCH 26/27] nail version of eslint due to upstream problems --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 289f5c9..7ae0cd9 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,9 @@ "swig": "^1.4.2" }, "devDependencies": { - "eslint": "^1.10.0", - "eslint-config-airbnb": "^3.1.0", - "eslint-plugin-react": "^3.15.0", + "eslint": "1.10.0", + "eslint-config-airbnb": "3.1.0", + "eslint-plugin-react": "3.15.0", "grunt": "0.4.5", "grunt-contrib-less": "^1.1.0", "grunt-contrib-uglify": "^0.11.0", From 29e5529179d8fbd239debbd1de0ca24f60b5fd00 Mon Sep 17 00:00:00 2001 From: OvisMaximus Date: Sat, 6 Jan 2018 22:32:18 +0100 Subject: [PATCH 27/27] nail version of grunt due to upstream problems --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ae0cd9..a0e8f86 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "grunt-contrib-uglify": "^0.11.0", "grunt-contrib-watch": "0.6.1", "grunt-develop": "^0.4.0", - "grunt-eslint": "17.3.1", + "grunt-eslint": "^17.3.0", "jquery": "^2.2.0", "jsdom": "^7.2.2", "load-grunt-tasks": "^3.4.0",