mileswilford / OLD ADC Monitor Improver

// ==UserScript==
// @name OLD ADC Monitor Improver
// @description Various ADC ticket monitor display improvements.
// @author mileswilford
// @version 3.2.0
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.2/moment.min.js
// @include http://monitor.drafthouse.com/monitor.aspx*
// @include https://monitor.drafthouse.com/monitor.aspx*
// ==/UserScript==

$(document).ready(function() {
    var ADC_MI_VERSION = "3.2.0";
    var STAGING_MODE = true;
	/* CONFIGURABLES ---------------------------------------------------------------- */
	var titleReplaces = [
	    	['Drafthouse Films: ', ''],
	    	['2D ', ''],
	    	['3D ', '3D ', 'show-3d'],
	    	['PRIVATE EVENT 59 min', 'PCE/TECH', 'pce-tech'],
	    	['PRIVATE EVENT 29 min', 'PCE/TECH', 'pce-tech'],
	    	['Private Party', 'Private Party', 'pce-tech'],
	    	['PCE Tech Check', 'PCE Tech Check', 'pce-tech'],
	    	['Girlie Night: ', ''],
	    	['Action Pack: ' , ''],
	    	['Mondo x Chiller: ', ''],
	    	['Advance Victory Screening: ', ''],
	    	['Victory Screening: ', ''],
	    	['Kids Camp: ', 'Kids: '],
	    	['Promo Screening: ', '']
		];
		
	var virtualScreens = {
		'0002' : 3,
		'0007' : 11,
		"0102" : 8,
		"0103" : 8,
		'0901' : 7,
		"1401" : 9
	};
	 /* END CONFIGURABLES ----------------------------------------------------------- */

	// Parses  times into miliseconds, conscientious of the fact that times before 5AM are tomorrow.    
	function timeParse(timeString) {
	    var timeFormat = "h:mm A";
		var parsedTimeMoment = moment(timeString, timeFormat);
		if (parsedTimeMoment.hour() < 5) {
			parsedTimeMoment.add(1, 'days');
		}
		
		return parsedTimeMoment.toDate().getTime();
	}
	
    function getURLParameter(param) {
	    var query = window.location.search.substring(1);
	       var vars = query.split("&");
	       for (var i = 0; i < vars.length ; i++) {
	               var pair = vars[i].split("=");
	               if (pair[0] == param){
	               	return pair[1];
	               }
	       }
	       return false;
 	}
 	
    // Returns the time as a numerical percent
    // Parses the time if it is fed a string
	function getTimePercent(time) {
		if (typeof time == 'string') {
			time = timeParse(time);
		}
		return ((time - firstAndLastTime[0])/(firstAndLastTime[1] - firstAndLastTime[0]) * 100);
	}
 	
 	// Add version number for reference and lines toggle
    $('body').append($("<div>").text("ADC MI " + ADC_MI_VERSION).css({
    	"position" : "fixed",
    	"bottom" : 0,
    	"right" : 0,
    	"opacity" : 0.3
    }).prepend("<label id='adcmi-gridlines'><input type='checkbox'/> Toggle Lines </label>"));
    
    // Add timer block
    $('body').append($("<div>").text("30").css({
        "position": "fixed",
        "bottom" : 0,
        "left" : 0,
        "opacity" : 0.6
    }).addClass('timer-block'));
    
    // Add scheduling mode toggle
    $('body').append($('<div><label id="adcmi-scheduling"><input type="checkbox" /> Scheduling Mode </label>').css({
        "position" : "fixed",
        "bottom" : 0,
        "left" : "4em",
        "opacity" : 0.6
    }));
    
    $('body').append($('<div id="adcmi-scheduling-info">').css({
        "position" : "absolute",
        "top" : 0,
        "right" : 0
    }));
    
    // The written instructions
    $('.theater').after($('<div>').text("Dark lines represent check drop time in the show. Dark area represents time elapsed.  Pink areas represent 30-minute preshows.")
    .css('text-align', 'center'));
    
	// Gets me the first and last times of the day in ms form in an array
	firstAndLastTime = (function () {
	    var alltimes = [];
	    $('.timestart, .timeend').each(function() {
	    	var theTime = $(this).text();
	    	
	    	alltimes.push(timeParse(theTime));
	    });
	
		alltimes.sort();
    	
    	return [alltimes[0], alltimes[alltimes.length-1]];
    })();
    
    // Moves left point 30 minutes left to allow for 1st preshow
    firstAndLastTime[0] -= 1000 * 60 * 30;
    
    // Some front-end cleanup and prep for my absolute positioning
    
    // Fade the virtual screen
    if (virtualScreens[getURLParameter('siteid')]) {
    	$('.screen:nth-of-type(' + virtualScreens[getURLParameter('siteid')] + ')')
    		.css({'opacity' : 0.35, 'border' : 0}); 
    }
    
    // Hide some stuff that is useless or rendered obsolete by this script
    $('.layout').hide();
    // Remove now-redundant intermissions
    $('.intermission').remove();   
    $('.format').remove();
    
    // Provide a box-space and adjust borders for screens
    $('.screen').css({
        'position' : 'relative', 'height' : 92
    }); 
    
    // Fixes firefox display bug related to box-sizing
    $('.seats').css({
        'position' : 'relative',
        'top' : '-4px'
    });
    
    // Will later set the positions of the screenings
    $('.screen > span').attr('style', '').css({
    	'position' : 'absolute',
    	'top' : 0,
    	'bottom' : 0,
    	'border': 0
    }).each(function() {
        var $this = $(this);
        var seats = $this.find('.seats').text().split(' ');
        var numSold = parseInt(seats[0], 10);
        var numLeft = parseInt(seats.pop(), 10);
        var seatingPercent = (numSold / (numSold + numLeft));
        var red;
        var green;
        var blue;
        var yellowPoint = 0.6;
        
        if (seatingPercent < yellowPoint) {
            // red = 255 * (seatingPercent / yellowPoint);
            // green = 255;
            // blue = 0;
            red = 150;
            green = 150;
            blue = 150;
        } else {
            red = 255;
            green = 255 * Math.pow((1 - seatingPercent)/(1 - yellowPoint), 1);
            blue = 0;
        }
        $after = $('<div>').css({
            'position' : 'absolute',
                'top' : 0,
                'bottom' : 0,
                'left' : 0,
                'right' : 0,
            'z-index' : -5
        });
        $after.css('background-color', 'rgba(' + Math.round(red) + ', ' + Math.round(green) + ', ' + Math.round(blue) + ', .3)');
        $this.append($after);
        $this.css('background-color', 'white');
        
    });
    
    $('div').css('border-color', 'transparent');
    

    
    // Provide a box-space for the count meter
    $('.theater').css({'position' : 'relative', 'overflow' : 'hidden'}) 
    	// Add theater numbers
    	.prepend($('<div>').addClass('theater-count').css({ 
	    	'position' : 'absolute',
	    	'top' : 0,
	    	'bottom' : 0
    	}));
    	
    // Format titles and execute titleReplaces
    $('.title').css('text-align', 'center').each(function() {
    	var title = $(this).text();
    	for (var i = 0; i < titleReplaces.length; i++) {
    		if (titleReplaces[i][2] && title.indexOf(titleReplaces[i][0]) != -1) {
    			// Later, these classes are hooks to run additional changes
    			$(this).addClass(titleReplaces[i][2]);
    		}
    		title = title.replace(titleReplaces[i][0], titleReplaces[i][1]);
    	}
    	$(this).text(title);
    });
    
    // Make sure Not On Sale shows are always the bottom of the stack
    $('.notonsale').each(function() {
    	$(this).parent().css({
    		'z-index': 0,
    		'background-color' : '#DDF'
    	});
    });
    
    // Add theater numbers
    var screenCounter = 0;
    $('.screen').each(function() {
    	screenCounter++;
    	$(this).prepend($('<div>').text(screenCounter).css({
    		'position' : 'absolute',
    		'top' : 0,
    		'bottom' : 0,
    		'left' : 0,
    		'font-size' : '4em'
    	}));
    });
    
	// Absolutely position each show's left and right sides
    $('.screen > span').each(function() {
    	var startTime = $(this).find('.timestart').text();
    	var endTime = $(this).find('.timeend').text();
    	
    	$(this).css({
    		'left'  : getTimePercent(startTime) + '%', 
    		'right' : (100 - getTimePercent(endTime)) + '%'
    	});
    });
    
    // Get the current time in ms, conscientious of the fact that times before 5AM are tomorrow.
    var now = (function() {
    	var current = moment();
    	if (current.hour() < 5) {
    	    current.add(1, 'days');
    	}
    	return current;
    })();
    
    // Create current time meter edge and position it absolutely
    // It does NOT need to move automatically - the monitor auto-refreshes too quickly for it to matter
    // First get the current date as YYYYMMDD
    var nowString = (function() {
    	var current = moment(now);
    	var year = current.year().toString();
    	var month = (function() {
    		var month = current.month() + 1;
    		if (month < 10) {
    			return "0" + month.toString();
    		} else {
    			return month.toString();
    		}
    	})();
    	var day = (function() {
    		var day = current.date();
    		if (day < 10) {
    			return "0" + day;
    		} else {
    			return day.toString();
    		}
    	})();
    	return year + month + day;
    })();
    
    // Check the URL parameters to make sure it is looking at current day
    var dateParam = getURLParameter('date');
    if (dateParam == nowString || !dateParam) {
    	// Insert the meter
	    $('.theater').append($('<div>').css({
	    	'background-color' : 'rgba(0, 0, 0, 0.15)',
	    	'pointer-events' : 'none',
	    	'padding-top' : $('.theater').height(),
	    	'position' : 'absolute',
	    		'top' : 0,
	    		'bottom' : 0,
	    		'left' : 0,
	    		'right' : 100-getTimePercent(now.toDate().getTime()) + '%',
	        'z-index' : 1000
	    }));
    }
    
    // Creates a check-drop line marker and position it absolutely. 
    // This must be done in pixels, which means the window will need to reload after it resizes.
    function minutesToPixels(minutes) {
    	var fourtyMinMs = minutes * 60 * 1000;
    	var fourtyMinPercent = getTimePercent(firstAndLastTime[0] + fourtyMinMs);
    	var theaterWidth = $('.theater').width();
    	return theaterWidth * (fourtyMinPercent/100);
    }
    $('.screen > span').append($('<div>').addClass('timedrop-line').css({
    	'background-color' : 'rgba(0, 0, 0, 0.4)',
    	'position' : 'absolute',
    		'top' : '1.8em',
    		'bottom' : 0,
    		'right' : minutesToPixels(40) + 'px',
        'width' : '2px',
        'z-index' : 1000
    })).prepend($('<div>').addClass('preshow-marker').css({
    	'position' : 'absolute',
    		'top' : 1,
    		'bottom' : 1,
    		'left' : -minutesToPixels(30)-1,
    	'width' : minutesToPixels(30)-1,
    	'background-color' : '#FDE',
    	'opacity' : 0.5
    }));
    
    $(window).resize(function() {
    	$('.timedrop-line').css('right', minutesToPixels(40) + 'px');
    	$('.preshow-marker').css({
    		'left' : -minutesToPixels(30)-1,
    		'width' : minutesToPixels(30)-1
    	})
    });
    
    // In this module, execute special show-based title-replace scripts
    (function() {
    	$('.pce-tech').parent().css("overflow", "hidden").find('.timedrop, .timedrop-line').remove();
    	$('.show-3d').parent().append($('<div>').css({
    		'background-image' : 'url(data:image/png;base64,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)',
    		'background-position' : 'center center',
    		'background-repeat' : 'no-repeat',
    		'background-opacity' : 0.5,
    		'pointer-events' : 'none',
    		'opacity' : 0.5,
    		'position' : 'absolute',
	    		'top' : 0,
	    		'bottom' : 0,
	    		'left' : 0,
	    		'right' : 0
    	}));
    })();
    
    
    // Buttons and configurations module
    (function() {
        function toggleBorders(borderType) {
            $('.adcmi-screen').css('border-top', "5px solid " + borderType);
        }
        $('#adcmi-gridlines').click(function() {
            if ($(this).find('input').is(':checked')) {
                toggleBorders('black');
                if (localStorage) { localStorage.setItem('showGridlines', 'true'); }
            } else {
                toggleBorders('transparent');
                if (localStorage) { localStorage.setItem('showGridlines', 'false'); }
            }
        });
        if (localStorage && localStorage.getItem('showGridlines') == 'true') {
            $('#adcmi-gridlines').click();
            toggleBorders('black');
        }
        
        function toggleSchedulingMode(onOff) {
            if (onOff) {
                $('.notonsale').parent().find('.preshow-marker').show();
                $('#adcmi-scheduling-info').show();
            } else {
                $('.notonsale').parent().find('.preshow-marker').hide();
                $('#adcmi-scheduling-info').hide();
            }
        }
        
        $('#adcmi-scheduling').click(function() {
            if ($(this).find('input').is(':checked')) {
                toggleSchedulingMode(true);
                if (localStorage) { localStorage.setItem('schedulingMode', 'true'); }
            } else {
                toggleSchedulingMode(false);
                if (localStorage) { localStorage.setItem('schedulingMode', 'false'); }
            }
        });
        if (localStorage && localStorage.getItem('schedulingMode') == 'true') {
            $('#adcmi-scheduling').find('input').prop('checked', true);
        } else {
            toggleSchedulingMode(false);
        }
    })();
    
    // Finally, make sure the site is reloading correctly in case the native auto reload isn't working.
    // Do this once every 30 seconds
    // TODO: reload in background?
    (function() {
        var timerCount = 30;
        var timerExpires = 0; // 35 seconds
        var $timerBlock = $('.timer-block');
        function refreshTimer() {
            timerCount--;
            if (timerCount <= timerExpires) { location.reload(); }
            else {
                $timerBlock.text(timerCount);
            }
        }
        if (!STAGING_MODE) {setInterval(refreshTimer, 1000);}
    })();
});