mileswilford / ADC Monitor Improver

// ==UserScript==
// @name ADC Monitor Improver
// @description Various ADC ticket monitor display improvements.
// @author mileswilford
// @version 4.3.4
// @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 *monitor.drafthouse.com/monitor.aspx*
// ==/UserScript==

var ADCMI_VERSION = '4.3.4';
var STAGING_MODE = false;

moment.fn.toLong = function() {
    return this.toDate().getTime();
};

var virtualScreenMap = {
	'Alamo at the Ritz' : 3,
	'Alamo Lakeline' : 11,
	"Alamo Mason Park" : 8,
	"Alamo Vintage Park" : 8,
	'Alamo Yonkers' : 7,
	"Alamo Lubbock" : 9,
	"Alamo Mainstreet" : 7,
    "Alamo Downtown Brooklyn" : 8
};

var titleLabels = [
	['Drafthouse Films: ', ''],
	['2D ', '','show-2d'],
	['3D ', '3D ', 'show-3d'],
	['PRIVATE EVENT 59 min', 'PCE/TECH', 'pce-tech'],
	['PRIVATE EVENT 29 min', 'PCE/TECH', 'pce-tech'],
	['PRIVATE EVENT 14 min', 'PCE/TECH', 'pce-tech'],
	['Private Party', 'Private Party', 'pce-tech'],
	['PCE Tech Check', 'PCE Tech Check', 'pce-tech'],
    ['Set up and Tech', 'Set up and Tech', 'pce-tech'],
	['Girlie Night: ', ''],
	['Action Pack: ' , ''],
	['Mondo x Chiller: ', ''],
	['Advance Victory Screening: ', ''],
	['Victory Screening: ', ''],
	['Kids Camp: ', 'Kids: '],
	['Promo Screening: ', ''],
	['Still Awesome: ', ''],
	['Tough Guy Cinema: ', 'Tough: '],
	['Master Pancake: ', 'Mas. Pan: '],
	['Open Caption: ', '', 'open-caption']
];

var IGNORE_TITLES = [
    '',
    'PCE/TECH',
    'Employee Screening'
];

function checkToIgnoreTitle(title) {
    return IGNORE_TITLES.indexOf(title) > -1;
}

var cssBlock = `
div, span {
    border-style: none;
    padding: 0;
}

p {
    font-size: 12px;
    text-align: center;
}


#adcmi-version {
    position: fixed;
    bottom: 0;
    right: 0;
    opacity: 0.4;
}

#adcmi-settings {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 100;
}

#adcmi-reloadTimer {
    position: fixed;
    bottom: 0;
    left: 0;
    opacity: 0.4;
}

div.theater {
    position: relative;
    margin-top: -1em;
    border-style: none;
    overflow-x: hidden;
}

#RadDatePicker1_wrapper ~ a {
    display: inline-block;
    background-color: white;
    position: relative;
    left: -230px;
    width: 210px;
}

.adcmi-screen {
    position: relative;
    height: 90px;
    z-index: 3;
    border: thin solid transparent;
}

.adcmi-timeline {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #000;
    z-index: 100;
    opacity: 0.2;
    pointer-events: none;
}

.adcmi-screen:nth-of-type(2n-1) {
    background-color: #F5F5F5;
}

.adcmi-theaterNum {
    font-size: 80px;
    line-height: 90px;
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    opacity: 0.5;
    z-index: 200;
}

.adcmi-show {
    display: inline-block;
    position: absolute;
        top: 2px;
        bottom: 2px;
    text-align: center;
}

.adcmi-showbacker {
    background-color: white;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    z-index: -1;
}

.adcmi-nonesold .adcmi-showbacker {
    border: thin solid #CCC;
}

.notonsale .adcmi-showbacker { border: 0; }


.adcmi-title {
    font-size: 1.4em;
    height: 1em;
    width: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.adcmi-seating-notonsale, .adcmi-notonsale { display: none; }

.adcmi-seating {
    font-weight: bold;
    font-size: 1.1em;
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
}

    .pce-tech .adcmi-seating,
    .adcmi-show.notonsale.pce-tech .adcmi-seating-notonsale { display: none; }

.adcmi-startTime, .adcmi-endTime {
    position: absolute;
    left: 0;
    bottom: 1em;
    white-space: nowrap;
}

.adcmi-endTime {
    bottom: 2em;
    right: 0;
    left: auto;
}

.adcmi-dropTime {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.8em;
}

    .pce-tech .adcmi-dropTime { display: none; }

.adcmi-preshow {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 100%;
    background-color: #FDE;
    border-right: 2px solid white;
    opacity: 0.6
    z-index: -1;
}

    .pce-tech .adcmi-preshow { display: none; }
    .notonsale .adcmi-preshow { opacity: 0.5; }

.adcmi-show.notonsale {
    color: black;
}
.adcmi-show.notonsale .adcmi-showbacker {
    background-color: #DDF;
}

.adcmi-checkdrop-line {
    position: absolute;
    border: thin solid black;
    bottom: 0;
    top: 0;
    opacity: 0.2;
}

    .pce-tech .adcmi-checkdrop-line { display: none; }

.adcmi-show.notonsale {
    z-index: -2;
}

.adcmi-show.notonsale .adcmi-seating { display: none; }
.adcmi-show.notonsale .adcmi-seating-notonsale,
.adcmi-show.notonsale .adcmi-notonsale { display: block; }

.adcmi-show.notonsale .adcmi-notonsale {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: blue;
    color: white;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    z-index: 1;
}

.adcmi-show.show-3d:before {
    content: " ";
    background-image: url(data:image/png;base64,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);
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-repeat: no-repeat;
    background-size: contain;
    background-position: center bottom;
    opacity: 0.4;
}

.adcmi-show.open-caption:before {
    content: "Open Caption";
    position: absolute;
    bottom: 0;
    right: 0;
    font-size: 0.7em;
    opacity: 0.7;
}

.adcmi-scope, .adcmi-flat {
    position: absolute;
    top: 0;
    left: -1em;
    width: 1em;
    height: 1em;
}

#adcmi-scheduleChecker {
    width: 20%;
    float: right;
    display: none;
}

    #adcmi-scheduleChecker.adcmi-schedulemode { display: block; }

#form1.adcmi-schedulemode {
    float: left;
    width: 80%;
}
`;

$(document).ready(function() {

    function getSchedulePercent(timeMoment, monitor) {
        var numerator = timeMoment.toLong() - monitor.firstMomentLong;
        var denominator = monitor.lastMomentLong - monitor.firstMomentLong;
        return ((numerator/denominator) * 100);
    }

    $('body').append($('<style type="text/css">').html(cssBlock));

    var CURRENT_TIME = (function() {
        var now = moment();
        if (now.hour() < 5) {
            now.subtract(1, 'days');
        }
        return now;
    })();

    function momentFromClock(clockString, dayMoment) {
	    var timeFormat = "YYYY-MM-DD h:mm A";
	    clockString = dayMoment.year() + '-' + (dayMoment.month()+1) + '-'
	        + dayMoment.date() + ' ' + clockString;
		var thisMoment = moment(clockString, timeFormat);

		if (thisMoment.hour() < 5) {
		    thisMoment.add(1, 'days');
		}

		return thisMoment;
    }

    function parseMonitor($grabDivs) {
        if (!($grabDivs instanceof $)) {
            return null;
        }
        var $theater = $grabDivs.last();
        var $monitorHeader = $theater.prev();
        $theater = $theater.find('.theater');
        var screen = 1;

        /* Prototype for parsed monitor object
         *  var monitor = {
         *      "firstMomentLong" : long
         *      "lastMomentLong" : long
         *      "storeLoc" : string
         *      "selectedDate" : moment
         *      "numScreens" : int
         *      "shows" : [{
         *          "screen" : int
         *          "title" : string
         *          "startTime" : string
         *          "startMoment" : moment
         *          "endTime" : string
         *          "endMoment" : moment
         *          "dropTime" : string
         *          "dropMoment" : moment
         *          numSold : int
         *          numLeft : int
         *          numHold: int
         *          label[] : string[]
         *
         *       }]
         *   }
         *
         *
         * A lot is about to happen all at once
         * -------------------------------------- */

        var monitor = {
            "storeLoc" : $monitorHeader.find('strong').text(),

            // Grab the date highlighted currently in the selector
            "selectedDate" : (function() {
            var dateString = $monitorHeader.find('#RadDatePicker1_dateInput_text').attr('value');
            var mdyArray = dateString.split('/');
            var now = moment();
            now.year(parseInt(mdyArray[2]))
                .month(parseInt(mdyArray[0])-1)
                .date(parseInt(mdyArray[1]))
                .startOf('day')
                .add(5, 'hours');
            return now;
        })(),

            // Process the shows by iterating first through screens, then show blocks
            // Keep track of which screen is being processed so they aren't mixed up later
            "shows" : (function() {
                var shows = [];
                $theater.find('.screen').each(function() {
                    var show = [];
                    $(this).find('.show, .seatslow, .seatssoldout').each(function() {
                        var title = $(this).find('.title').text();
                        var startTime = $(this).find('.timestart').text();
                        var endTime = $(this).find('.timeend').text();
                        var dropTime = $(this).find('.timedrop').text();
                        var seats = $(this).find('.seats').text();
                        var seatsSplit= seats.split(' ');
                        var numSold = parseInt(seatsSplit[0], 10);
                        // If numSold is NAN, that means NOT ON SALE.  Check if anything has already sold
                        if (isNaN(numSold)) {
                            var numSoldRegEx = /(\d{1,3}) SOLD/;
                            var seatsMatch = seats.match(numSoldRegEx);
                            // Only execute if the regex matches, otherwise 0 sold
                            if (seatsMatch !== null) {
                                numSold = parseInt(seatsMatch[1]);
                            } else {
                                numSold = 0;
                            }
                        }
                        var numLeft = parseInt(seatsSplit.pop(), 10);
                        var numHoldRegEx = /\((\d{1,3})hold\)/;
                        var holdMatch = seatsSplit[1].match(numHoldRegEx);
                        if (holdMatch !== null) {
                            numHold = parseInt(holdMatch[1]);
                        } else {
                            numHold = 0;
                        }
                        var labels = [];
                        (function() {
                            var orgTitle = title;
                            for (var tRId in titleLabels) {
                                var orgSubStr = titleLabels[tRId][0];
                                var replaceWith = titleLabels[tRId][1];
                                var addLabel = titleLabels[tRId][2];
                                if (orgTitle.indexOf(orgSubStr) != -1) {
                                    title = title.replace(orgSubStr, replaceWith);
                                    if (addLabel) {
                                        labels.push(addLabel);
                                    }
                                }
                            }
                        })();

                        if ($(this).has('.notonsale').length > 0) {
                            labels.push('notonsale');
                        }

                        // notonsale, 3D, victory screening, etc
                        shows.push({
                            "screen" : screen,
                            "title" : title,
                            "startTime" : startTime,
                            "endTime" : endTime,
                            "dropTime" : dropTime,
                            "numSold" : numSold,
                            "numLeft" : numLeft,
                            "numHold" : numHold,
                            "labels" : labels
                        });
                    });
                screen++;
                });
                return shows;
            })(),
        'numScreens' : screen-1
        };

        for (var i = 0; i < monitor.shows.length; i++) {
            monitor.shows[i].startMoment = momentFromClock(monitor.shows[i].startTime, monitor.selectedDate);
            monitor.shows[i].endMoment = momentFromClock(monitor.shows[i].endTime, monitor.selectedDate);
            monitor.shows[i].dropMoment = momentFromClock(monitor.shows[i].dropTime, monitor.selectedDate);
        }

    monitor.lastMomentLong = (function() {
        monitor.shows.sort(function(b, a) {
           if (a.endMoment.isBefore(b.endMoment)) {
               return -1;
           }  else if (b.endMoment.isBefore(a.endMoment)) {
               return 1;
           } else {
               return 0;
           }
        });

        return monitor.shows[0].endMoment;
    })().toLong();

    monitor.firstMomentLong = (function() {
        monitor.shows.sort(function(a, b) {
           if (a.startMoment.isBefore(b.startMoment)) {
               return -1;
           }  else if (b.startMoment.isBefore(a.startMoment)) {
               return 1;
           } else {
               return 0;
           }
        });
        return monitor.shows[0].startMoment;
    })().toLong() - 1000 * 60 * 30;

        return monitor;
    }

    var $grabDivs = $('#form1 > div');
    var monitor = parseMonitor($grabDivs);
    //Page Loaded @ [\d:]+

    function buildSchedule(monitor) {

        $theater = $('.theater');
        $theater.empty();
        for (var i = 1; i <= monitor.numScreens; i++) {
            $theater.append($(`<div id="adcmi-screen-${i}">`)
                .addClass('adcmi-screen').html(
                    `<div class="adcmi-theaterNum">${i}</div>`));
        }
        for (var showId in monitor.shows) {
            var show = monitor.shows[showId];

            var seatingPercent = show.numSold / (show.numSold+show.numLeft);
            var red, green, blue;
            var yellowPoint = 0.6;

            if (show.numSold + show.numHold == 0) {
                red = 255;
                green = 255;
                blue = 255;
            } else 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;
            }

            $target = $(`#adcmi-screen-${show.screen}`);
            $append = $('<div>');
            $append.addClass('adcmi-show').css({
                "left" : getSchedulePercent(show.startMoment, monitor) + "%",
                "right" : (100-getSchedulePercent(show.endMoment, monitor)) + "%"
            });
            var holdString = (show.numHold > 0) ? `(${show.numHold} hold) ` : '';
            $append.html(`
                <h1 class="adcmi-title">${show.title}</h1>
                <div class="adcmi-startTime">${show.startTime}</div>
                <div class="adcmi-endTime">${show.endTime}</div>
                <div class="adcmi-dropTime">${show.dropTime}</div>
                <div class="adcmi-seating">${show.numSold} ${holdString}// ${show.numLeft}</div>
                <div class="adcmi-seating-notonsale">${show.numSold} Sold</div>
                <div class="adcmi-notonsale">NOT ON SALE</div>
                <div class="adcmi-preshow"></div>
                <div class="adcmi-checkdrop-line"></div>
                <div class="adcmi-showbacker"></div>
            `).css('background-color', 'rgba(' + Math.round(red) + ', '
                + Math.round(green) + ', ' + Math.round(blue) + ', .3)');
            for (var labelId in show.labels) {
                $append.addClass(show.labels[labelId]);
            }

            if (show.numSold + show.numHold == 0) {
                $append.addClass('adcmi-nonesold');
            }
            $target.append($append);
        }

        (function() {
            function setWidths() {
                var pixelsPerMinute = $theater.width() * ((1000 * 60)
                    / (monitor.lastMomentLong - monitor.firstMomentLong));
                $('.adcmi-preshow').width(pixelsPerMinute * 30);
                $('.adcmi-checkdrop-line').css('right', pixelsPerMinute * 40);
            }
            setWidths();
            $(window).resize(function() {setWidths();});
        })();

        $theater.append($('<div>').addClass('adcmi-timeline').css(
            "right", (100-getSchedulePercent(moment(), monitor)) + "%"));

        $(`#adcmi-screen-${virtualScreenMap[monitor.storeLoc]}`).css('opacity', 0.4);

        $('#RadDatePicker1_dateInput_text').val(
            `${monitor.selectedDate.get('month') + 1}/${monitor.selectedDate.get('date')}/${monitor.selectedDate.get('year')}`
        );

        if (monitor.storeLoc == "Alamo Mason Park") {
            (function() {
                $.getJSON("https://gist.githubusercontent.com/manofconviction/b3ce294610cfde01b8df1f9293aa8e34/raw", function(data) {
                    var scopeList = data.scopeTitles;
                    var flatList = data.flatTitles;
                    $('.adcmi-title').each(function() {
                        var $this = $(this);
                        var title = $this.text();
                        if (scopeList.indexOf(title) != -1) {
                            $this.parent().append($(`<div class="adcmi-scope">S</div>`));
                        }
                        if (flatList.indexOf(title) != -1) {
                            $this.parent().append($(`<div class="adcmi-flat">F</div>`));
                        }
                    });
                });
            })();
        }
    }

    buildSchedule(monitor);

    function buildScheduleChecker() {
        var $schedulingChecker = $('#adcmi-scheduleChecker');
        $schedulingChecker.empty();
        var uniqueShows = [];
        var showsWithTimes = {};
        var PCE_TECH = 'PCE/TECH';

        var firstShowtime;
        var firstShowId = 0;
        if (checkToIgnoreTitle(monitor.shows[firstShowId].title)) {
            do {
                firstShowtime = monitor.shows[firstShowId + 1].startMoment;
                firstShowId++;
            } while (monitor.shows[firstShowId].title == PCE_TECH);
        } else {firstShowtime = monitor.shows[firstShowId].startMoment; }

        var lastShowtime;
        var lastShowId = monitor.shows.length - 1;
        if (checkToIgnoreTitle(monitor.shows[lastShowId].title)) {
            do {
                lastShowtime = monitor.shows[lastShowId - 1].startMoment;
                lastShowId--;
            } while (monitor.shows[lastShowId].title == PCE_TECH);
        } else {lastShowtime = monitor.shows[lastShowId].startMoment; }

        for (var showId in monitor.shows) {
            var show = monitor.shows[showId];
            if (!uniqueShows.includes(show.title)) {
                uniqueShows.push(show.title);
                showsWithTimes[show.title] = [];
            }
            showsWithTimes[show.title].push(show.startMoment);
        }
        $schDl = $('<dl>');
        $schedulingChecker.append($schDl);
        for (id in uniqueShows) {
            var title = uniqueShows[id];
            if (checkToIgnoreTitle(title)) { continue; }
            var times = showsWithTimes[uniqueShows[id]];
            $schDl.append($(`<dt class="adcmi-schd-chk-${id}">${title}</dt>`));
            for (var i = 0; i < times.length; i++) {
                var thisTime = times[i];
                var formattedTime = thisTime.format("hh:mm A");
                var breaksContinuity = (i+1 < times.length && Math.abs(times[i].diff(times[i+1], 'minutes')) > 240);
                var continuityStr = (breaksContinuity) ? ' continuity broken after' : '';
                var tooClose = (i+1 > times.length && Math.abs(times[i].diff(times[i+1], 'minutes')) < 60);
                var fairlyPlayedStr = '';
                var closenessStr = (tooClose) ? ' too close!' : '';
                if (times.length > 1) {
                    if (i == 0 && thisTime.diff(firstShowtime, 'minutes') > 135) {
                        fairlyPlayedStr += ' first round is too late';
                    }
                    if (i == times.length - 1 && Math.abs(thisTime.diff(lastShowtime, 'minutes')) > 135) {
                        fairlyPlayedStr += ' last round is too early';
                    }
                }
                $schDl.append($(
                    `<dd>${formattedTime}${continuityStr}${closenessStr}${fairlyPlayedStr}</dd>`
                ));
            }
        }
    }


    (function() {
        var $versionDiv = $('<div id="adcmi-version">').text(`ADCMI V${ADCMI_VERSION}`);
        var $settingsDiv = $(`<div id="adcmi-settings">`).append($(
            `<label id="adcmi-toggleLines"><input type="checkbox" /> Toggle Lines</label>
             <label id="adcmi-toggleSchedCheck"><input type="checkbox" /> Toggle Schedule Checker</label>`
        ));
        var $timerDiv = $(`<div id="adcmi-reloadTimer">`).text("30");
        var $schedulingChecker = $(`<div id="adcmi-scheduleChecker">`);
        $('body').append($versionDiv).append($settingsDiv)
            .append($timerDiv).prepend($schedulingChecker);

        function toggleBorders(borderType) {
            $('.adcmi-screen').css('border', "thin solid " + borderType);
        }
        $('#adcmi-toggleLines').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');
        }

        $('#adcmi-toggleSchedCheck input').click(function() {
            $('#form1').toggleClass('adcmi-schedulemode');
            $schedulingChecker.toggleClass('adcmi-schedulemode');
            if($(this).is(':checked')) { buildScheduleChecker(); }
        });

        var timerCount = 30;

        function refreshTimer() {
            timerCount--;
            if (timerCount < 1) {
                $timerDiv.text("Reloading...");
                var $newMonitorStorage = $('<div id="adcmi-holder-div">');
                var target = window.location.href + " #form1";
                $newMonitorStorage.load(target, function() {
                    var $grabDivs = $newMonitorStorage.find('#form1 > div');
                    var newMonitor = parseMonitor($grabDivs);
                    buildSchedule(newMonitor);
                    buildScheduleChecker();
                    timerCount = 31;
                });

            } else {
                $timerDiv.text(timerCount);
            }
        }
        setInterval(refreshTimer, 1000);
    })();
});