// ==UserScript==
// @name Turkmaster (Mturk) w/ PRE counter
// @author DonovanM
// @namespace gotodonovan's GF page for this idk
// @description A page-monitoring web app for Mturk (Mechanical Turk) designed to make turking more efficient. Easily monitor mturk search pages and requesters and Auto-Accept the HITs you missed.
// @include https://www.mturk.com/mturk/*
// @version 1.4.5
// @require https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js
// @require https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js
// @grant GM_getValue
// @grant GM_setValue
// @downloadURL https://update.sleazyfork.org/scripts/22824/Turkmaster%20%28Mturk%29%20w%20PRE%20counter.user.js
// @updateURL https://update.sleazyfork.org/scripts/22824/Turkmaster%20%28Mturk%29%20w%20PRE%20counter.meta.js
// ==/UserScript==
var settings = (function() {
var LOCAL_STORAGE = "turkmaster_settings";
var pub = {
sound : true,
animation : true,
preloadHits : false,
volume : 50,
notifications : true,
alertOnly : false,
fontSize : 10,
typeface : "Oxygen",
desktopNotifications : false,
canHide : false,
stopOnCaptcha : true
}
_load();
function _setFontSize(val) {
if (val >= 5 && val <= 20) {
pub.fontSize = val;
$("#dispatcher div").css("font-size", val + "pt");
$(".notification_panel p").css("font-size", val + "pt");
$("#settingsDialog, #settingsDialog div, #settingsDialog li, #settingsDialog input, #settingsDialog button").css("font-size", val + "pt");
_save();
}
}
function _setDesktopNotifications(val, callback) {
if (val) {
requestDesktopNotifications(function(isPermitted) {
callback(isPermitted);
pub.desktopNotifications = isPermitted;
_save();
});
pub.desktopNotifications = false;
} else {
pub.desktopNotifications = false;
}
_save();
}
function _setVolume(val) {
if (val >= 0 && val <= 100) {
Sound.setVolume(val);
pub.volume = val;
_save();
}
}
function _save() {
// localStorage.setItem(LOCAL_STORAGE, JSON.stringify(pub));
GM_setValue(LOCAL_STORAGE, JSON.stringify(pub));
}
function _load() {
var values = GM_getValue(LOCAL_STORAGE);
if (typeof values === 'undefined')
values = localStorage.getItem(LOCAL_STORAGE);
if (values) {
values = JSON.parse(values);
for (i in values)
pub[i] = values[i];
}
}
pub.setFontSize = _setFontSize;
pub.setVolume = _setVolume;
pub.setDesktopNotifications = _setDesktopNotifications;
pub.save = _save;
return pub;
}());
var pageType = {
MAIN : true, // This is so remote watcher requests don't add new watchers to multiple pages and cause mturk errors.
DASHBOARD : false,
HIT : false,
REQUESTER : false,
SEARCH : false
};
var loadError = false;
var wasViewed = false;
var dispatch;
var notificationPanel;
if(!('contains' in String.prototype)) {
String.prototype.contains = function(str, startIndex) {
return -1 !== String.prototype.indexOf.call(this, str, startIndex);
};
}
$(document).ready(function(){
checkPageType();
loadFonts();
if (pageType.DASHBOARD) {
dispatch = new Dispatch();
DispatchUI.create(dispatch);
createDetailsPanel();
IgnoreList.init();
if (settings.preloadHits)
loadDefaultWatchers();
else
dispatch.load();
requestMain();
preloadImages();
addFormStyle();
}
if (pageType.HIT || pageType.REQUESTER || pageType.SEARCH)
addWatchButton();
notificationPanel = new NotificationPanel();
// Listen to messages
window.addEventListener('storage', onStorageEvent, false);
});
$(window).on('beforeunload', function() {
if (pageType.DASHBOARD && pageType.MAIN) {
dispatch.save();
}
});
function loadFonts() {
WebFont.load({
google: { families: [ 'Oxygen:400,700:latin', 'Droid Sans Mono' ] }
});
}
function onStorageEvent(event) {
if (event.key.substring(0,13) === "notifier_msg_")
onMessageReceived(event.key.substring(13), JSON.parse(event.newValue).content);
}
function checkPageType() {
// Dashboard, hit, requester, search
if (document.URL === "https://www.mturk.com/mturk/dashboard")
pageType.DASHBOARD = true;
else if (document.URL.match(/https:\/\/www.mturk.com\/mturk\/(preview|accept).+groupId=.*/) !== null)
pageType.HIT = true;
else if (document.URL.match(/requesterId=([A-Z0-9]+)/) !== null)
pageType.REQUESTER = true;
else if (document.URL.match(/(searchbar|findhits)/) !== null)
pageType.SEARCH = true;
}
function requestMain() {
sendMessage({ header: "request_main" });
}
function preloadImages() {
var images = [
'https://i.imgur.com/guRzYEL.png',
'https://i.imgur.com/5snaSxU.png',
'https://i.imgur.com/VTHXHI4.png',
'https://i.imgur.com/peEhuHZ.png'
];
$(images).each(function(){
$('')[0].src = this;
});
}
var SettingsDialog = function() {
var DOMElement,
widgetGroups = [
{
id : 'soundSettings',
text : 'Sound',
type : 'toggle',
setting : 'sound',
items : [
{
id : 'volume',
text : 'Volume (0 - 100)',
type : 'text',
setting : 'volume',
handler : function (target, value) {
settings.setVolume(value);
}
}
]
},
{
id : 'notificationSettings',
text : 'Notifications',
type : 'toggle',
setting : 'notifications',
items : [
{
id : 'desktopNotifications',
text : 'Desktop Notifications',
type : 'toggle',
setting : 'desktopNotifications',
handler : _handleDesktopNotificationToggle
}
]
},
{
id : 'more',
text : 'Captchas',
items : [
{
id : 'stopOnCaptcha',
text : 'Stop on Captcha',
type : 'toggle',
setting : 'stopOnCaptcha'
}
]
},
{
id : 'fontSettings',
text : 'Font',
items : [
{
id : 'fontSize',
text : 'Size (pt)',
type : 'text',
setting : 'fontSize',
handler : function (target, value) {
settings.setFontSize(value);
}
}
]
},
{
id : 'uiSettings',
text : 'User Interface',
items : [
{
id : 'hideable',
text : 'Hideable',
type : 'toggle',
setting : 'canHide',
handler : function (target, value) {
settings.canHide = value;
setTimeout(function () { DispatchUI.setHide() }, 50);
}
}
]
},
{
id : 'backup',
text : 'Backup',
items : [
{
type : 'more',
id : 'export',
text : 'Export',
handler: _showExport
},
{
type : 'more',
id : 'import',
text : 'Import',
handler: _showImport
}
]
}
];
function _show() {
if (!DOMElement)
_createDOMElement()
_setCurrentSettings(widgetGroups);
DOMElement.show();
$(window).on('click', _handleWindowClick);
}
function _isVisible() {
if (DOMElement)
return DOMElement.is(":visible");
else
return false;
}
// Sets the widgets to reflect the current settings
function _setCurrentSettings(list) {
list.forEach(function (item) {
if (item.type)
_setSetting(item.id, item.type, item.setting);
if (item.items)
_setCurrentSettings(item.items);
})
}
function _setSetting(id, type, setting) {
var widget = DOMElement.find("#" + id),
value = settings[setting];
if (type === "toggle" && value) {
widget.children(".on_off").addClass("on");
} else if (type === "text") {
widget.children("input").val(value);
}
}
function _cancel() {
DOMElement.hide();
}
function _createDOMElement() {
_addStyle();
DOMElement = $('
Settings
').append(_createGroups(widgetGroups));
_addHandlers();
$("body").append(DOMElement);
}
// Creates settings groups for each group item in groups
function _createGroups(groups) {
return groups.map(function (group) {
return _createGroup(group);
});
}
function _addDefaultHandlers(items) {
items.forEach(function (item) {
if (item.type && !item.handler)
item.handler = _setValue;
if (item.items)
_addDefaultHandlers(item.items);
})
}
function _setValue(target, value) {
settings[this.setting] = value;
}
function _createGroup(params) {
var widgetHTML = widgetHTML || "",
group,
list;
if (params.type === 'toggle')
widgetHTML = getToggleHTML();
group = $('
');
}
function _createToggleItem(id, text) {
return $('
' + getToggleHTML() + text + '
');
}
function _createMoreItem(id, text) {
return $('
' + text + '
');
}
function getToggleHTML() {
return '';
}
function _addHandlers() {
DOMElement.on('click', function(e) {
if (e.target.tagName === "BUTTON" || e.target.parentNode.tagName === "BUTTON")
_handleWidgetClick(e);
});
DOMElement.on('change', _handleInputChange);
_addDefaultHandlers(widgetGroups);
}
function _handleWindowClick(e) {
var target = e.target;
// Hides the settings dialog if user clicks outside of the dialog
if (!DOMElement.is(target) && DOMElement.has(target).length === 0 && $("#settings img").get(0) !== target) {
_cancel();
$(window).off('click', _handleWindowClick);
}
}
function _handleInputChange(e) {
var target = $(e.target),
value = target.val(),
id = target.parent().attr('id');
_setWidget(id, target, value);
}
function _handleWidgetClick(e) {
e.preventDefault();
// Chrome returns the span as the target while FF returns the button
var target = (e.target.tagName === "BUTTON") ? $(e.target) : $(e.target).parent(),
value = target.hasClass("on"),
id = target.parent().attr('id');
// Toggle widget unless it's for desktop notifications (because it's asynchronous)
if (id !== "desktopNotifications")
value = _toggle(target);
_setWidget(id, target, value);
}
function _setWidget(id, target, value) {
_findWidget(widgetGroups, id).handler(target, value);
settings.save();
}
function _toggle(element) {
if (element.hasClass("on")) {
element.removeClass("on");
return false;
} else {
element.addClass("on");
return true;
}
}
// Recursively looks for a widget with specified id
function _findWidget(widgets, id, i) {
return widgets.reduce(function (prev, curr) {
return (prev !== null) ? prev :
(curr.id === id) ? curr :
(curr.items) ? _findWidget(curr.items, id) :
null;
}, null);
}
function _handleDesktopNotificationToggle(target, value) {
if (value)
target.removeClass("on");
// Desktop notification requests require user action so we need a callback
// for when the user responds.
settings.setDesktopNotifications(!value, function(isPermitted) {
if (isPermitted) {
target.addClass("on");
} else {
target.removeClass("on");
console.error("Desktop notifications are blocked.");
}
});
}
function _showExport() {
var div = $('
Export Watchers
Copy the text below. (Triple-click to highlight all)
' + dispatch.exportWatchers() + '
');
$('')
.click(function() { div.remove() })
.appendTo(div);
div.appendTo($("body"));
}
function _showImport() {
var div = $('
',
"Turkmaster"
);
// Adding the data URL inline wouldn't work for some reason, so I'm doing it this way.
// Image from http://latierrasenosestrecha.org/wp-content/themes/purity/img/icons/settings.png
$("img", settingsBtn)[0].src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpFRUQ3N0Q2NkUyQjJFMDExOTM4OUZBRkY5RUM4NjkxMiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFQjNFQjA2OEIyRTYxMUUwOUZDRUUxRERBNzIzQkY1NyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFQjNFQjA2N0IyRTYxMUUwOUZDRUUxRERBNzIzQkY1NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFRkQ3N0Q2NkUyQjJFMDExOTM4OUZBRkY5RUM4NjkxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFRUQ3N0Q2NkUyQjJFMDExOTM4OUZBRkY5RUM4NjkxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PiP+YRgAAALDSURBVHja7FdPZB1BGJ9tawmt8HinrUQpjdBqqFaq5BChJBI5xCvVQ1mPRq89hGqkeusxpLpKDtFTqpEorUZKKdFcWo2WR7REwyOE8niUeP19/Jbxmd3NbjXv0Aw/szO733z/ft/MrNdqtUw72zHT5tZ2A04UEYqiaBTdPFAC9oDb1Wp15TAjMEflhv3cP4kAPC2j6wK24OEv61WgPg2U3El054BtyO2m6fCSqgCLDKBbBjqBHWAQ+A3cAmYcItPAAp/XgDOAGD0GI97nMgDKRel3K8yGuRbP/BSHxMCGkhMjemBEPQ8HetUica79jJT6Djlx5nxeEn4GagnvNiSswCl45UnP8UbC99vAehEOSA4/AmUrvPeBF8A9YJgEFQVvgMdABXhgRUoIeBWGbhUtw07rOWRkvgB3qNywrwKfGIVJJb9fdB+oWJ6Ih8LkRRLR1WT+JfAO+GBxonLgMkTYx9E9cxBpBLgO3OW4xt1vHTJDlIkj8oQGPFdrSBWFkFlKi8CsQ7khicat8YQolwf0qyrso4yWcVTRbFYKAofgPpTsWWRsYrzJiL2yKiNuZZLP1YIsA1ybxXEoKlmLdmB8kd6PcK7f+n7XMtZkra/PgpAHS5eaFwUrZL+0RRgRMtfD6jASwg4k7AeTefaBGda0NMnzTeBrincx0XpYDdc49xCRmi5ShpHkm89D9PRGSn5lfoJkjZU3uU6hfcDnwRK3p0zFBS5at/Iacb5fMb2Rsm8kG4Dwd6B7q8ItBj3iES0p6eNZ0Mfxa773VUWs8XTNdSERlp9NeHeZO6IYepBLT0CZ1Twp+EZCaYI1M5Q1HXJyH9jMlQJev8YoLO0HcAXoBqYS1pri+0v83lg3onru45ghLvFqVcMiDWu+5TDaUxzq/as7YcZl9afaVneg6PRhXstDVYZh0Wu5d/Rv+N8b8EeAAQBg+uBx8hdn9gAAAABJRU5ErkJggg==";
$( '#watcher_container' ).prepend('
PRE: 0
');
},
addActions: function() {
var dispatch = DispatchUI.dispatch,
ctrl = DispatchUI.div.find("#controller");
$("div.play_all", ctrl).mousedown(function() {
dispatch.start(true);
});
$("div.play", ctrl).mousedown(function() {
dispatch.start();
});
$("div.pause", ctrl).mousedown(function() {
dispatch.stop();
});
},
addListeners: function() {
var dispatch = DispatchUI.dispatch;
var div = DispatchUI.div;
dispatch.addListener(Evt.ADD, function(watcher) {
// This could be done on one line, but then we would lose access to the WatcherUI's internal Watcher object and functionality
var watcher = WatcherUI.create(watcher);
$("#watcher_container", DispatchUI.div).append(watcher.element);
DispatchUI.watchers.push(watcher);
// watchers.push(WatcherUI.create(watcher).appendTo($("#watcher_container", div)));
});
dispatch.addListener(Evt.REMOVE, function(watcher) {
// Remove watcher from array
var index = -1,
watchers = DispatchUI.watchers;
for (var i = 0, len = watchers.length; i < len; i++) {
if (watchers[i].watcher === watcher) {
index = i;
break;
}
}
if (index !== -1)
DispatchUI.watchers.splice(index, 1);
});
DispatchUI.setHide = function() {
if (settings.canHide) {
$(window).on('click', handleWindowClick);
} else {
$(window).off('click', handleWindowClick);
}
}
DispatchUI.setHide();
function handleWindowClick(e) {
if (!div.is(e.target) && div.has(e.target).length === 0 && $(".notification_panel").has(e.target).length === 0 && !$("#settingsDialog").is(e.target) && $("#settingsDialog").has(e.target).length === 0) {
hide();
$(window).off('click', handleWindowClick);
$(div).on('click', handleDivClick);
}
}
function handleDivClick(e) {
show();
$(div).off('click', handleDivClick);
if (settings.canHide)
$(window).on('click', handleWindowClick);
}
function hide() {
div.addClass("tm-hidden");
$("#content_container").addClass("full");
$("#details_panel").addClass("left");
}
function show() {
div.removeClass("tm-hidden");
$("#content_container").removeClass("full");
$("#details_panel").removeClass("left");
}
},
addStyle: function() {
addStyle("#dispatcher { background-color: #f5f5f5; position: fixed; top: 0px; float: left; left: 0; height: 100%; width: 270px; font-size: 8pt; margin-left: 0px; transition: left 0.5s ease; }\
#dispatcher.tm-hidden { left: -240px }\
#content_container { position: absolute; left: 270px; top: 0; right: 0; border-left: 2px solid #dadada; transition: left 0.5s ease; }\
#content_container.full { left: 30px }\
#dispatcher #controller { text-align: center; font: 160% Candara, sans-serif; color: #585858; position: relative; padding: 3px 5px; }\
#dispatcher #controller .on_off { margin: 6px 5px 0 0 }\
#dispatcher #controller .on_off a { font-size: 80% }\
#dispatcher #controller #settings { top: 2px; position: absolute; right: 5px; }\
#dispatcher #controller #settings img { width: 1.5em }\
#dispatcher #controller .play_container { position: absolute; left: 5px }\
#dispatcher #watcher_container { position: absolute; top: 30px; bottom: 0; overflow-y:auto; width: 100%;}\
#dispatcher #watcher_container p { margin: 30px 0px }\
#dispatcher #watcher_container .error_button a { text-decoration: none; color: #555; background-color: #fff; padding: 3px 10px; margin: 5px; border: 1px solid #aaa; border-radius: 2px }\
#dispatcher #watcher_container .error_button a:hover { background-color: #def; border-color: #aaa }\
#dispatcher div { font-size: 7pt }\
#dispatcher .watcher {\
box-sizing: border-box;\
margin: 3px 3px 0;\
background-color: #fff;\
position: relative;\
border-bottom: 1px solid #ddd;\
border-right: 1px solid #ddd;\
top: 0;\
transition: background-color 0.5s, top 0.1s;\
-moz-user-select: none;\
-webkit-touch-callout: none;\
-webkit-user-select: none;\
-khtml-user-select: none;\
}\
#dispatcher .watcher.dragging { cursor: grabbing; z-index: 100; opacity: 0.8; transition: background-color 0.5s, top 0s }\
#dispatcher .watcher div { font: " + settings.fontSize + "pt 'Oxygen', verdana, sans-serif }\
#dispatcher .watcher.running .details { background-color: #BADFF2 }\
#dispatcher .watcher.updated { background-color: rgba(218, 240, 251, 1); }\
#dispatcher .watcher .details { width: 25px; text-align: center; float: right; background-color: rgba(234, 234, 234, 1); position: absolute; top: 0; bottom: 0; right: 0; font-size: 90%; color: #fff; transition: background-color 0.5s }\
#dispatcher .watcher .details.updated { background-color: rgba(218, 240, 251, 1); background-color: #F0FF60; }\
#dispatcher .watcher .name { font-size 130%; color: black; text-decoration: none; display: inline-block; margin-top: -3px}\
#dispatcher .watcher .name:hover { text-decoration: underline }\
#dispatcher .watcher.dragging .name:hover { text-decoration: none }\
#dispatcher .watcher .time { display: block; float: left; font-size: 80% }\
.on_off { float: right; cursor: pointer }\
.on_off a { color: #333; margin: 1px; font-size: 56%; font-weight: bold }\
.on_off a:nth-child(2) { background-color: #aeaeae; color: #fff; border-radius: 12px; padding: 3px 6px; }\
.on_off.on a:nth-child(1) { background-color: #55b8ea; color: #fff; border-radius: 12px; padding: 3px 6px; }\
.on_off.on a:nth-child(2) { background-color: inherit; color: #333; border-radius: inherit; padding: inherit; }\
.watcher .on_off { }\
#dispatcher .watcher > .content { margin-right: 25px; padding: 5px 5px 5px 33px;}\
#dispatcher .watcher .bottom { margin: 0 0 -5px; color: #aaa }\
#dispatcher .watcher .bottom a:link { color: black; }\
#dispatcher .watcher .bottom a:hover { color: #cef; }\
#dispatcher .watcher .details { font-size: 150%; font-weight: bold }\
#dispatcher .watcher .last_updated { position: absolute; right: 30px; bottom: 4px; font-size: 80% }\
#dispatcher .watcher .icons { visibility: hidden; margin-left: 10px; bottom: 5px }\
#dispatcher .watcher:hover .icons { visibility: visible }\
#dispatcher .watcher .icons img { opacity: 0.2; height: 0.9em }\
#dispatcher .watcher .icons img:hover { opacity: 1 }\
#dispatcher .watcher .color_code { position: absolute; left: 0; top: 0; bottom: 0; width: 9px; cursor: grab;}\
#dispatcher .watcher .color_code div { position: absolute; left: 0; top: 0; bottom: 0; width: 5px; transition: width 0.15s; }\
#dispatcher .watcher.dragging .color_code { cursor: grabbing; }\
#dispatcher .watcher .color_code:hover div { width: 9px }\
#dispatcher .watcher .color_code.hit div { background-color: rgba(234, 111, 111, .7); }\
#dispatcher .watcher .color_code.requester div { background-color: rgba(51, 147, 255, .7); }\
#dispatcher .watcher .color_code.url div { background-color: rgba(57, 221, 122, .7); }\
.watcher .play_container {\
padding: 0px 0px 0px 12px;\
float: left;\
cursor: default;\
}\
.play, .pause, .play_all {\
width:20px;\
height: 20px;\
position: relative;\
display: block;\
}\
#controller .play, #controller .pause, #controller .play_all { float: left; }\
.play:before, .play_all:before {\
width: 0;\
height: 0;\
border-width: 8px 11px;\
border-style: solid;\
border-color: transparent transparent transparent #747474;\
position: absolute;\
content: '';\
top: 3px;\
left: 0px;\
}\
.play.selected:after {\
width: 6px;\
height: 6px;\
border-radius: 1px;\
position: absolute;\
content: '';\
background-color: #999;\
top: 13px;\
right: 9px;\
}\
.play_all:after {\
width: 0;\
height: 0;\
border-width: 8px 11px;\
border-style: solid;\
border-color: transparent transparent transparent #999;\
position: absolute;\
content: '';\
top: 3px;\
left: 5px;\
}\
.watcher.running .play:before, .watcher.running .play:after, .pause:before, .pause:after {\
width: 4px;\
height: 15px;\
background: #747474;\
position: absolute;\
content: '';\
top: 3px;\
}\
.watcher.running .play:before, .pause:before {\
left: 0px;\
border: none;\
}\
.watcher.running .play:after, .pause:after {\
left: 6px;\
}\
.play_select {\
width: 6px;\
height: 6px;\
border: 2px solid #cecece;\
border-radius: 2px;\
margin-top: 2px;\
}\
.watcher.selected .play_select { background-color: #55b8ea; border-color: #b4e6ff; }\
");
},
addDragAndDrop: function() {
// Drag watchers
var startY, currentBaseY, max, min, height,
dragDiv, nextDiv, prevDiv, startPos, endPos, isDragging,
slop = 7, currentWatcher, watchers = DispatchUI.watchers;
DispatchUI.div.on("mousedown", ".watcher", function(e) {
isDragging = false;
// Get the position of the watcher in the listing
startPos = endPos = $("#watcher_container .watcher").index(e.currentTarget);
// Get reference to the selected watcher
currentWatcher = watchers[startPos];
dragDiv = currentWatcher.element;
nextDiv = dragDiv.next();
prevDiv = dragDiv.prev();
// TODO Check target to prevent dragging from a component inside the watcher (i.e. buttons, links, etc.)
height = dragDiv.outerHeight(true);
startY = e.clientY;
currentBaseY = dragDiv.offset().top;
// max = Math.min($("#watcher_container").outerHeight(true), height * (DispatchUI.dispatch.watchers.length + .75)) - height;
min = watchers[0].element.offset().top;
max = Math.min($("#watcher_container").outerHeight(true), height * (watchers.length - 1) + watchers[0].element.offset().top);
$(window).on("mousemove", move);
$(window).on("mouseup", up);
});
function move(e) {
var offsetY = e.clientY - startY;
if (!isDragging && (Math.abs(offsetY) > slop)) {
// Start dragging
isDragging = true;
dragDiv.addClass("dragging");
}
if (isDragging) {
dragDiv.css('top', offsetY);
var diffY = dragDiv.offset().top - currentBaseY;
if (dragDiv.offset().top > max) {
dragDiv.offset({ top: max });
diffY = 0;
} else if (dragDiv.offset().top < min) {
dragDiv.offset({ top: min });
diffY = 0;
}
if (diffY > height / 2) {
// Move down one spot
nextDiv.offset({ 'top': nextDiv.offset().top - height });
nextDiv = nextDiv.nextAll(":not(.dragging)").first();
prevDiv = (prevDiv.length) ? prevDiv.nextAll(":not(.dragging)").first() : (dragDiv !== watchers[0].element) ? watchers[0].element : watchers[1].element;
currentBaseY += height;
endPos++;
} else if (-diffY > height / 2) {
// Move up one spot
prevDiv.offset({ 'top': prevDiv.offset().top + height });
prevDiv = prevDiv.prevAll(":not(.dragging)").first();
nextDiv = (nextDiv.length) ? nextDiv.prevAll(":not(.dragging)").first() : (dragDiv !== watchers[watchers.length - 1].element) ? watchers[watchers.length - 1].element : watchers[watchers.length - 2].element;
currentBaseY -= height;
endPos--;
}
}
}
function up(e) {
$(window).off("mousemove", move);
$(window).off("mouseup", up);
if (isDragging) {
e.preventDefault();
dragDiv.removeClass("dragging");
isDragging = false;
// $("div", colorCode).css('width', '');
dragDiv.css('cursor', '');
dragDiv.css('z-index', '');
dragDiv.css('opacity', '');
$(".name", dragDiv).removeClass("no_hover");
// Reset all watcher offsets
$("#watcher_container .watcher").css('transition', "background-color 0.5s, top 0s");
$("#watcher_container .watcher").css('top', '');
setTimeout(function() { $("#watcher_container .watcher").css('transition', ''); }, 600);
if (startPos !== endPos) {
if (endPos > startPos)
dragDiv.insertAfter($("#watcher_container .watcher")[endPos]);
else
dragDiv.insertBefore($("#watcher_container .watcher")[endPos]);
DispatchUI.dispatch.moveWatcher(startPos, endPos);
// Re-arrange our watchers array
watchers.splice(startPos, 1);
watchers.splice(endPos, 0, currentWatcher);
}
}
}
}
}
/** Dispatch object. Controls all of the watchers.
**/
function Dispatch() {
this.watchers = new Array();
this.isLoading = false;
// Listeners
this.listener = {
onadd: [],
onremove: [],
onstart: [],
onstop: [],
onchange: []
};
}
Dispatch.prototype = new Evt();
Dispatch.prototype.start = function(startAll) {
if (this.watchers.length > 0) {
var count = 0;
for (var i = 0, len = this.watchers.length; i < len; i++) {
// Don't start them all at the same time. There is a 2 second delay
// between each start. It had to be done in a self-executing function
// in order for the setTimeout to work properly.
if (this.watchers[i].state.isSelected || startAll) {
(function (watcher, x){
watcher.timer = setTimeout(function() { watcher.start(); }, x * 0000); // Let's try 0ms
})(this.watchers[i], count++);
}
}
}
this.notify(Evt.START, null);
}
Dispatch.prototype.stop = function() {
// Stop all Watchers
if (this.watchers.length > 0) {
for (var i = 0, len = this.watchers.length; i < len; i++)
this.watchers[i].stop();
}
this.interruptStart = true;
this.notify(Evt.STOP, null)
}
Dispatch.prototype.add = function(watcher) {
var self = this;
watcher.addListener(Evt.CHANGE, function() {
self.save();
})
this.watchers.push(watcher);
if (!this.isLoading) {
this.save();
}
this.notify(Evt.ADD, watcher);
// TODO Add a listener to save the watcher list after a watcher has been changed
return watcher;
}
Dispatch.prototype.save = function() {
if (!loadError) {
// localStorage.setItem('notifier_watchers', JSON.stringify(dispatch.watchers, Watcher.replacerArray));
GM_setValue('notifier_watchers', JSON.stringify(dispatch.watchers, Watcher.replacerArray));
var lastChecked = getLastChecked(dispatch.watchers);
if (lastChecked > 0)
localStorage.setItem('notifier_watchers_lastChecked', JSON.stringify(lastChecked));
}
function getLastChecked(watchers) {
var lastChecked = (watchers[0].date) ? watchers[0].date.getTime() : 0;
for (var i = 1, len = watchers.length; i < len; i++) {
if (watchers[i].isRunning)
return new Date.getTime();
if ((watchers[i].date) && (watchers[i].date.getTime() > lastChecked))
lastChecked = watchers[i].date.getTime();
}
return lastChecked;
}
}
Dispatch.prototype.load = function() {
this.isLoading = true;
var data;
var watchers,
lastChecked = localStorage.getItem('notifier_watchers_lastChecked');
data = GM_getValue('notifier_watchers');
if (typeof data === 'undefined')
data = localStorage.getItem('notifier_watchers');
if (data !== null) {
try {
watchers = JSON.parse(data);
try {
lastChecked = JSON.parse(lastChecked);
} catch(e) {
lastChecked = null;
}
var now = new Date().getTime(),
expTime = 180000, // 3 minutes
expired = (lastChecked !== null) ? now - lastChecked > expTime : false; // Expired if most recent watcher update happened more than x minutes before page was loaded
// Add the watchers. Clear last hits if past the expiration time
for(var i = 0; i < watchers.length; i++) {
if (expired)
watchers[i].lastHits = [];
this.add(new Watcher(watchers[i]));
}
} catch(e) {
loadError = true;
console.error("Error loading saved list", e);
}
} else {
loadDefaultWatchers();
}
this.isLoading = false;
}
Dispatch.prototype.remove = function(watcher) {
var index = this.watchers.indexOf(watcher);
if (index !== -1)
this.watchers.splice(index, 1);
watcher.delete();
this.save();
this.notify(Evt.REMOVE, watcher);
}
Dispatch.prototype.moveWatcher = function(from, to) {
if ((to >= 0 && to < this.watchers.length) && (from >= 0 && from < this.watchers.length)) {
var watcher = this.watchers.splice(from, 1);
this.watchers.splice(to, 0, watcher[0]);
this.save();
}
}
Dispatch.prototype.getWatcherByProperty = function(name, value) {
if (this.watchers.length > 0) {
for (var i = 0, len = this.watchers.length; i < len; i++) {
if (this.watchers[i][name] === value)
return this.watchers[i];
}
}
return null;
}
Dispatch.prototype.getWatcherIndex = function(watcher) {
return this.watchers.indexOf(watcher);
}
Dispatch.prototype.getWatcher = function(index) {
return this.watchers[index];
}
Dispatch.prototype.getWatcherCount = function() {
return this.watchers.length;
}
Dispatch.prototype.hideWatchers = function() {
$("#controller a").css('display', "none");
$("#watcher_container").html("");
$("#watcher_container")
.css('background-color', "#f9f9f9")
.css('color', "#ff6b6b")
.css('text-align', "center").append(
$("
").text("There is already a notifier running on a different page."),
$("