Greasy Fork is available in English.

mTurkThemes

This script provides a UI that allows you to make and share themes for mTurk.com

// ==UserScript==
// @name            mTurkThemes
// @namespace       http://ericfraze.com
// @version         1.2
// @description     This script provides a UI that allows you to make and share themes for mTurk.com
// @match           https://www.mturk.com/*
// @resource        cssfile https://magnilucent.github.io/mTurk-Themes/css/style.css?version=71234123
// @resource        colpickcssfile https://magnilucent.github.io/mTurk-Themes/css/colpick.css?version=17
// @grant           GM_addStyle
// @grant           GM_getResourceText
// @grant           GM_setClipboard
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_deleteValue
// @copyright       2014+, Eric Fraze
// @require         http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js
// @require         https://greasyfork.org/scripts/1562-colpick-color-picker/code/colpick%20Color%20Picker.js?version=3858
// ==/UserScript==

// Add color picker CSS
GM_addStyle(GM_getResourceText("colpickcssfile"));

// The CSS used to apply the themes
var CSSText;

// Holds all the current color states
var variables;

// Groups the variables by color value
var colorGroups;

// Name of current theme
var currentTheme;

// Hiddden "theme" that holds the unsaved changes of a theme
// TODO: Make sure no one can name a name 'unsavedTheme'.
var unsavedTheme = "unsavedTheme";

// The version of the script on last save
var savedVariableVersion;

// The version of the script on last save
var currentVariableVersion = 5;

// List of all themes installed
var themeNames;


// Get last save script version
savedVariableVersion = GM_getValue('savedVariableVersion', -1);

// Check to see if there is a current theme. If not, this is probably the first run.
if (GM_getValue('mturk-current-theme', -1) === -1){
	// Set to default theme if there is no current theme.
	currentTheme = "Default";
}else{
	// Get the current theme name.
	currentTheme = GM_getValue('mturk-current-theme');
}

// Load theme names
if (GM_getValue('mturk-theme-names', -1) === -1){
	themeNames = ['Default'];
}else{
	themeNames = JSON.parse(GM_getValue("mturk-theme-names"));
}

/*****************************************
* Load current theme
******************************************/
// Set variables to defaults
resetVariables();

// Apply the theme
applyTheme(currentTheme);

// Wait for DOM to load
$(document).ready(function() {
	// Add the text used to open the panel
	$("#user_name_field").after(' | <span id="mturkthemeoptions" class="header_links">Theme Options</span>');

	// Add the mTurkTheme panel
	$("body").append('<div id="mturktheme" class=""></div>');

	// Add the toolbar (import/save/undo buttons, ect)
	addToolbar();

	// Add the wrapper for the color list
	$("#mturktheme").append('<div class="color-list-wrapper"></div>');

	// Make the color picker plugin apply on "DOMNodeInserted"
	bindColorPicker();

	// Refresh the color list
	refreshList();
});

// Toolbar has the buttons and theme select
function addToolbar(){
	$("#mturktheme").append('<div class="toolbar"></div>');
	$("#mturktheme .toolbar").append('<div title="New theme" class="icon add"></div>');
	$("#mturktheme .toolbar").append('<select class="theme-select"></select>');
	$("#mturktheme .toolbar").append('<div title="Import theme" class="icon import"></div>');
	$("#mturktheme .toolbar").append('<div title="Export theme" class="icon export"></div>');
	$("#mturktheme .toolbar").append('<div title="Hide" class="icon close"></div>');
	$("#mturktheme .toolbar").append('<div title="Save theme" class="icon save"></div>');
	$("#mturktheme .toolbar").append('<div title="Revert to last save" class="icon revert"></div>');

	 updateThemeList();
}

// Update the theme list in the toolbar
function updateThemeList() {
	var unsavedFlag = "";

	$("#mturktheme .toolbar .theme-select").empty();

	for (var i in themeNames){
		if ( (themeNames[i] == currentTheme) && (GM_getValue('mturk-theme-data-' + unsavedTheme, -1) !== -1) )
			unsavedFlag = "*";
		else
			unsavedFlag = "";

		$("#mturktheme .toolbar .theme-select").append('<option value="' + themeNames[i] + '">' + unsavedFlag + themeNames[i] + unsavedFlag +'</option>');
	}

	$("#mturktheme .toolbar .theme-select").val(currentTheme);
}

// Binds the color picker to each new color list element
function bindColorPicker() {
	// Dynamically binds the color picker to each new color list item
	$("#mturktheme .color-list-wrapper").bind('DOMNodeInserted', function() {
		// Binds the colorpicker to a list item
		$("#mturktheme .variable-group-wrapper .color").colpick({
			// Sets the color of the color picker to the list item's color
			onBeforeShow:function() {
				var oldColor;

				// Returns an RBG version of the background
				oldColor = $(this).css('background-color');

				// Single out the three colors
				oldColor = oldColor.replace("rgb(", "").replace(")","");

				// Make an array of the colors
				oldColor = oldColor.split(", ");

				// This function requires an object to be passed, so send one over.
				$(this).colpickSetColor( { 'r':oldColor[0], 'g':oldColor[1], 'b':oldColor[2] } );
			},
			// Changes the color in the CSS and applies the changes
			onSubmit:function(hsb,hex,rgb,el) {
				var oldColor, variableName;

				// Hides the color picker
				$(el).colpickHide();

				// Using the browser to make the old and new colors the same text value, then comparing them
				$("#mturkthemeoptions").css("border-color", "#" + hex);
				if ( $(el).css('background-color') != $("#mturkthemeoptions").css("border-color") ) {
					// Check if the list item is a single item or a group item
					if ($(el).parent('.variable-color').length) {
						// Single items have the variable name as their name
						variableName = $(el).attr('name');

						// Need to get the color dynamically
						oldColor = variables[getPropertyIndex(variableName)][1];

						// Change the CSS
						setProperty(variableName, oldColor, "#" + hex);

						// Old code that updated the list item. May use this again when RefreshList is updated.
						//$(this).css('background-color', newColor);
					}else{
						// Group items have the color value as their name
						oldColor = $(el).attr('name');


						// Changes the CSS of the entire group
						setGroupProperty(oldColor, "#" + hex);
						
						// Old code that updated the list item. May use this again when RefreshList is updated.
						//$('#mturktheme .color[style="background-color: ' + color + ';"]').css('background-color', newColor);
						//$(this).parent().children(".header").text(newColor);
					}

						// Adds the CSS to the page
						applyCSS();

						// Refreshes the color list
						refreshList();

						// Saves unsaved changes
						// Seems ironic but they need to be saved so they persist through page loads.
						saveTheme(unsavedTheme);

						// Update the theme list to add the unsaved marker
						updateThemeList();
				}
			}
		});
	});
}

$("#mturktheme .variable-color").live('mouseenter', function () {
	var color = $(this).find(".color");
	// Single items have the variable name as their name
	variableName = $(color).attr('name');

	// Need to get the color dynamically
	oldColor = variables[getPropertyIndex(variableName)][1];

	// Change the CSS
	setProperty(variableName, null, "red");

	// Adds the CSS to the page
	applyCSS();
});

$("#mturktheme .variable-color").live('mouseleave', function () {
	var color = $(this).find(".color");
	// Single items have the variable name as their name
	variableName = $(color).attr('name');

	// Need to get the color dynamically
	oldColor = variables[getPropertyIndex(variableName)][1];

	// Change the CSS
	setProperty(variableName, null, oldColor);

	// Adds the CSS to the page
	applyCSS();
});

// Clicking the text makes mTurkTheme slide
$("#mturkthemeoptions").live('click', function () {
	$("#mturktheme").addClass("active");
});

// Expand group list items
$("#mturktheme .variable-group .expand").live('click', function () {
	$(this).parents(".variable-group-wrapper").toggleClass("active");
});

// Add a theme
$("#mturktheme .toolbar .add").live('click', function () {
	var themeName = prompt("Enter the theme name:");
	addTheme(themeName);
});

// Import a theme
$("#mturktheme .toolbar .import").live('click', function () {
	// Get the stringified variables
	var newTheme = prompt("Paste in the theme!");

	// Make sure the user didn't cancel.
	if (newTheme != null) {
		// Reset variables to defaults
		resetVariables();

		// Load variables from stringified variables
		variables = JSON.parse(newTheme);

		// Load changes in unsaved theme so they can be reverted
		saveTheme(unsavedTheme);

		// Apply the changes
		// applyTheme will detect the unsaved changes and load theme
		applyTheme(currentTheme);

		// Refresh the color list
		refreshList();

		// Update the theme list to add the unsaved marker
		updateThemeList();
	}
});

// Switch themes when they are selected in the dropdown box
$("#mturktheme .toolbar .theme-select").live('change', function () {
	// Delete unsaved changes
	deleteTheme(unsavedTheme);

	// Get the theme name
	currentTheme = $("#mturktheme .toolbar .theme-select :selected").attr('value');

	// Set the current theme
	GM_setValue('mturk-current-theme', currentTheme);

	// Apply the theme
	applyTheme(currentTheme);

	// Refresh color list
	refreshList();

	// Update the theme list to add the unsaved marker
	updateThemeList();
});

// Export a theme
$("#mturktheme .toolbar .export").live('click', function () {
	// Set the stringified variables to the clipboard
	GM_setClipboard(JSON.stringify(variables));
	alert("Theme copied to clipboard. Pastebin and share!");
});

// Revert to last save
$("#mturktheme .toolbar .revert").live('click', function () {
	// Delete unsaved changes
	deleteTheme(unsavedTheme);

	// Apply last save
	applyTheme(currentTheme)

	// Refresh color list
	refreshList();

	// Update the theme list to add the unsaved marker
	updateThemeList();
});

// Save theme
$("#mturktheme .toolbar .save").live('click', function () {
	saveTheme(currentTheme);
	deleteTheme(unsavedTheme);

	// Update the theme list to add the unsaved marker
	updateThemeList();
});

// Close the panel
$("#mturktheme .toolbar .close").live('click', function () {
	$("#mturktheme").removeClass("active");
});

function resetVariables() {
	// All of the color variables
	variables = [
		["hit-capsule-title-color", ""],
		["hit-capsule-title-hover-color", ""],
		["hit-capsule-title-visited-color", ""],
		["hit-capsule-link-right-color", ""],
		["hit-capsule-link-right-hover-color", ""],
		["hit-capsule-link-right-visited-color", ""],
		["hit-capsule-field-title-color", ""],
		["hit-capsule-field-text-color", ""],
		["header-link-color", ""],
		["subtab-text-color", ""],
		["separator-text-color", ""],
		["searchbar-text-color", ""],
		["whatis-link-color", ""],
		["dashboard-and-workerID-text-color", ""],
		["if-you-re-not-text-color", ""],
		["link-default-color", ""],
		["link-default-hover-color", ""],
		["link-default-visited-color", ""],
		["show-earnings-details-text-color", ""],
		["button-background-color", ""],
		["button-border-color", ""],
		["button-text-color", ""],
		["tab-text-color", ""],
		["tab-background-color-inactive", ""],
		["tab-border-color-inactive", ""],
		["tab-background-color-active", ""],
		["tab-border-color-active", ""],
		["page-background-color", ""],
		["tab-text-color-active", ""],
		["page-header-background-color", ""],
		["page-header-border-color", ""],
		["default-text-color", ""],
		["search-go-button-background-color", ""],
		["search-go-button-border-color", ""],
		["search-go-button-text-color", ""],
		["table-body-background-color", ""],
		["table-header-background-color", ""],
		["table-header-text-color", ""],
		["table-body-border-color", ""],
		["table-list-text-color", ""],
		["table-list-header-background-color", ""],
		["table-list-header-text-color", ""],
		["table-list-row-even-background-color", ""],
		["table-list-row-border-color", ""],
		["table-list-row-odd-background-color", ""],
		["table-link-color", ""],
		["sort-button-text-color", ""],
		["hit-border-color", ""],
		["hit-header-unqualified-background-color", ""],
		["hit-body-unqualified-background-color", ""],
		["hit-header-qualified-background-color", ""],
		["hit-body-qualified-background-color", ""],
		["page-footer-background-color", ""],
		["footer-text-color", ""],
		["footer-link-text-color", ""],
		["details-link-text-color", ""],
	];

	// Used to dynamically group the list items
	colorGroups = [];
}

// Returns the style element that holds the theme
function getStyle() {
	// Find the correct style element by looking for my comment
	return $( "style:contains('/* mTurk Theme */')" );
}

// Applies CSSText to the style element
function applyCSS() {
	// Replace the text of the style element
	getStyle().text(CSSText);
}

// Makes the regex string used to find and change properties in the CSS
function findPropertyRegex(variableName, flags) {
	// Fancy thing lets flags be optiional.
	flags = typeof flags !== 'undefined' ? flags : 'g';

	// Ugh. Regex. I need to escape the escapes here. Crazy.
	// Select the CSS property by the comment in the line above it.
	// NOTE: This relies on the property you want to change being the FIRST value
	// "border: 1px blue solid ;" will change "1px", not "blue"
	// "border: blue 1px solid;" will change "blue"
	// Group 1: comment & property
	// Group 2: property you want to change (I hope!)
	// Group 3: values after the property
	// Group 4: the semicolon
	var regstring = "(.*\\/\\*\\s" + variableName + "\\s\\*\\/\\s+.*:\\s+)(#*[A-Za-z0-9]+)(.*)(;.*)";

	// Create the RegEx object
	return new RegExp(regstring, flags);
}

// Edits the CSS to set a property
function replaceProperty(regex, str, value) {
	return str.replace(regex, "$1" + value + "$3$4");
}

// Finds the index of a property in variables
function getPropertyIndex (variableName) {
	for (var i in variables) {
		if (variables[i] != null) {
			if (variableName == variables[i][0]) {
				return i;
			}
		}
	}

	return -1;
}


// Gets a property from the CSS
// TODO: I think this function may be pretty unnessary and is slowing down the script
// The "variables" variable has the values too as long as its been applied
function getProperty(variableName) {
	// Get the CSS property regex
	regex = findPropertyRegex(variableName, '');
	// Get the groups
	groups = regex.exec(CSSText);
	if (groups != null) {
		// Return the value
		return groups[2];
	}else{
		console.log("Couldn't find " + variableName);
		var i = getPropertyIndex(variableName);
		delete variables[i];
		return -1;
	}
}

// Set an individual property to a color
function setProperty(variableName, oldColor, newColor) {
	// Get the CSS property regex
	regex = findPropertyRegex(variableName);

	// Replace the color with the one we want.
	CSSText = replaceProperty(regex, CSSText, newColor);

	// Update the color grouping if needed
	if (oldColor !== null) {
		var index = getPropertyIndex(variableName);

		variables[index][1] = newColor;
		
		if (newColor in colorGroups) {
			colorGroups[newColor].push(index);
		}else{
			colorGroups[newColor] = [];
			colorGroups[newColor][0] = index;
		}
		
		var groupIndex = colorGroups[oldColor].indexOf(index);
		delete colorGroups[oldColor][groupIndex];
	}
}

// Set a group of colors to another color
function setGroupProperty (oldColor, newColor) {
	// Update the color grouping
	for (var i in colorGroups[oldColor]) {
		index = colorGroups[oldColor][i];
		variableName = variables[index][0];
		setProperty(variableName, oldColor, newColor);
	}

	delete colorGroups[oldColor];
}

// Refreshes the color lust
// TODO: Make this only update what is needed instead of closing all color groups.
function refreshList () {
	var savedVariables = getSavedVariables(currentTheme);
	// Remove the old color pickers
	$(".colpick").remove();

	// Empty the list
	$("#mturktheme .color-list-wrapper").empty();

	// Refill the list
	for (var color in colorGroups) {
		var colorGroup = colorGroups[color];
		var appendString = "";
		var appendColorString = "";
		var unsavedChanges = false;
		

		for (var ii in colorGroup) {
			var index = colorGroup[ii];
			var variableName = variables[index][0];
			var unsavedText = "";
			if (savedVariables != -1) {
				if (color != savedVariables[index][1]){
					unsavedChanges = true;
					unsavedText = " *";
					console.log("Unsaved color");
				}else{
					unsavedText = "";
				}
			}
			
			appendColorString += '<div class="variable-color"><div class="content">' + variableName + unsavedText + '</div>'
			appendColorString += '<div name = "' + variableName + '" class="color" style="background-color: ' + color + ';"></div></div>';
		}

		if (unsavedChanges) {
			unsavedText = " *";
		}else{
			unsavedText = "";
		}

		appendString = '<div class = "variable-group-wrapper"><div class="variable-group">';
		appendString += '<div class="content">' + color + unsavedText + '</div><div name = "' + color + '" class="color" style="background-color: ' + color + ';"></div>';
		appendString += '<div class="expand"></div></div>';
		appendString += appendColorString;
		appendString += '</div>';
		$("#mturktheme .color-list-wrapper").append(appendString);
	}
}

// Add a theme
// TODO: Make sure the theme doesn't exist.
// TODO: Set the current theme to the new theme
function addTheme(themeName){
	// Add the theme name to the array
	themeNames.push(themeName);
	// Update the theme list to show the added theme
	updateThemeList();
	// Save the name names in local storage
	GM_setValue('mturk-theme-names', JSON.stringify( themeNames ));
	// Save the theme (uses current variables)
	saveTheme(themeName);
}

function deleteTheme(themeName) {
	GM_deleteValue('mturk-theme-data-' + themeName);
}

function saveTheme(themeName) {
	GM_setValue('savedVariableVersion', currentVariableVersion);
	GM_setValue('mturk-theme-data-' + themeName, JSON.stringify(variables));
}

function loadTheme(themeName, overrideUnsaved) {
	if (overrideUnsaved == null)
		overrideUnsaved = false;
	// Check for an unsaved theme
	if ( ( getSavedVariables(unsavedTheme) !== -1 ) && !overrideUnsaved ) {
		console.log("Loading unsaved theme changes");
		variables = getSavedVariables(unsavedTheme);
	}else{
		// Check for an existing theme
		if (getSavedVariables(themeName) !== -1){
				console.log("Loading saved theme");
				variables = getSavedVariables(themeName);
		}else{
			// Load the default CSS if all else fails.
			console.log("Loading default CSS");
			CSSToVariables();
		}
	}
}

function getSavedVariables(themeName) {
	return JSON.parse(GM_getValue('mturk-theme-data-' + themeName, -1));
}

// Apply a theme
function applyTheme(themeName) {
	resetVariables();

	currentTheme = themeName;

	// Get original CSS text
	CSSText = GM_getResourceText("cssfile");
	if (savedVariableVersion != currentVariableVersion){
		console.log("New version!");

		if (getSavedVariables(themeName) !== -1){
			loadTheme(currentTheme, true);
			VariablesToCSS();
			CSSToVariables();
			saveTheme(themeName);
		}

		if (getSavedVariables(unsavedTheme) !== -1){
			loadTheme(currentTheme);
			VariablesToCSS();
			CSSToVariables();
			saveTheme(unsavedTheme);
		}
	}else{
		// Load theme's variables
		loadTheme(currentTheme);

		// Applies the variables to the CSS
		VariablesToCSS();
	}

	// Add the CSS to the page
	if (getStyle().length){
		applyCSS();
	}else{
		GM_addStyle(CSSText);
	}
}

// Applies the variables to the CSS
function VariablesToCSS() {
	var variableName;
	var color;
	for (var i in variables) {
		if (variables[i]) {
			variableName = variables[i][0];
			
			color = variables[i][1];
			if (color == "") {
				color = getProperty(variableName);
			}

			setProperty(variableName, null, color);
			
			if (color in colorGroups) {
				colorGroups[color].push(i);
			}else{
				colorGroups[color] = [];
				colorGroups[color][0] = i;
			}
		}
	}
}

// Converts a CSS string to a variable list.
function CSSToVariables() {
	resetVariables();
	// Load all the color values.
	var variableName;
	var color;
	for (var i in variables) {
		variableName = variables[i][0];
		color = getProperty(variableName);
		console.log(variableName + " | " + color);
		if (color != -1){
			variables[i][1] = color;
			
			if (color in colorGroups) {
				colorGroups[color].push(i);
			}else{
				colorGroups[color] = [];
				colorGroups[color][0] = i;
			}
		}
	}
}