<commit_before>//@program
/*
  Copyright 2014 DouZen, Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
var i2h = function(i)
{	var answer = Math.floor(i).toString(16);
	return answer;
}

//255 in -> 255 out:
var  HSVtoHex = function(h255,s255,v255)
{	var rgb = HSVtoRGB255(h255,s255,v255)
	return RGBtoHex(rgb[0], rgb[1], rgb[2]);
}

var  RGBtoHex = function(r,g,b)
{	var hex= "#" +
		this.i2h(r/16) + "" + this.i2h(r%16) + "" +
		this.i2h(g/16) + "" + this.i2h(g%16) + "" +
		this.i2h(b/16) + "" + this.i2h(b%16);
	return hex;
}

var  RGBAtoHex = function(r,g,b,a255)
{	var answer= "rgba(" +  r + "," + g + "," + b + ",.5" +  a255/255 + ")";
	return answer;
}

var randomHexColorAlpha = function(a)				{return RGBAtoHex(rand(0,255), rand(0,255), rand(0,255), a);}
var randomHexColor 		= function()				{return RGBtoHex(rand(0,255), rand(0,255), rand(0,255));}
var randomHexColorDark 	= function()				{return RGBtoHex(rand(0,115), rand(0,115), rand(0,115));}
var randomHexColorMed 	= function()				{return RGBtoHex(rand(60,165), rand(60,165), rand(60,165));}
var randint 			= function( min, max ) 		{return Math.round( (Math.random() * (max - min)) + min );}

var HSVtoRGB255 = function (h255, s255, v255)
{	var h = h255*360/255;
	var s = s255/255;
	var v = v255/255;
	var rgb = [255,255,255];

    if(s <= 0) {       // < bad case - protect
        return rgb;
    }

    while(h <0) 	h += 360;
    while(h >= 360) h -= 360;
    h /= 60.0;
    var i = Math.floor(h);
    var ff = h - i;
    var p = v * (1 - s);
    var q = v * (1 - (s * ff));
    var t = v * (1 - (s * (1 - ff)));
	var r = 255;
	var g = 255;
	var b = 255;

    switch(i) {
    case 0:
        r = v;
        g = t;
        b = p;
        break;
    case 1:
        r = q;
        g = v;
        b = p;
        break;
    case 2:
        r = p;
        g = v;
        b = t;
        break;
    case 3:
        r = p;
        g = q;
        b = v;
        break;
    case 4:
        r = t;
        g = p;
        b = v;
        break;
    case 5:
    default:
        r = v;
        g = p;
        b = q;
        break;
    }

    rgb = [	Math.round(r*255),
    		Math.round(g*255),
    		Math.round(b*255)];

    return rgb;
}
<commit_msg>XS6 fixes<commit_after>//@program
/*
  Copyright 2014 DouZen, Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
var i2h = function(i)
{	var answer = Math.floor(i).toString(16);
	return answer;
}

//255 in -> 255 out:
var  HSVtoHex = function(h255,s255,v255)
{	var rgb = HSVtoRGB255(h255,s255,v255)
	return RGBtoHex(rgb[0], rgb[1], rgb[2]);
}

var  RGBtoHex = function(r,g,b)
{	var hex= "#" +
		i2h(r/16) + "" + i2h(r%16) + "" +
		i2h(g/16) + "" + i2h(g%16) + "" +
		i2h(b/16) + "" + i2h(b%16);
	return hex;
}

var  RGBAtoHex = function(r,g,b,a255)
{	var answer= "rgba(" +  r + "," + g + "," + b + ",.5" +  a255/255 + ")";
	return answer;
}

var randomHexColorAlpha = function(a)				{return RGBAtoHex(rand(0,255), rand(0,255), rand(0,255), a);}
var randomHexColor 		= function()				{return RGBtoHex(rand(0,255), rand(0,255), rand(0,255));}
var randomHexColorDark 	= function()				{return RGBtoHex(rand(0,115), rand(0,115), rand(0,115));}
var randomHexColorMed 	= function()				{return RGBtoHex(rand(60,165), rand(60,165), rand(60,165));}
var randint 			= function( min, max ) 		{return Math.round( (Math.random() * (max - min)) + min );}

var HSVtoRGB255 = function (h255, s255, v255)
{	var h = h255*360/255;
	var s = s255/255;
	var v = v255/255;
	var rgb = [255,255,255];

    if(s <= 0) {       // < bad case - protect
        return rgb;
    }

    while(h <0) 	h += 360;
    while(h >= 360) h -= 360;
    h /= 60.0;
    var i = Math.floor(h);
    var ff = h - i;
    var p = v * (1 - s);
    var q = v * (1 - (s * ff));
    var t = v * (1 - (s * (1 - ff)));
	var r = 255;
	var g = 255;
	var b = 255;

    switch(i) {
    case 0:
        r = v;
        g = t;
        b = p;
        break;
    case 1:
        r = q;
        g = v;
        b = p;
        break;
    case 2:
        r = p;
        g = v;
        b = t;
        break;
    case 3:
        r = p;
        g = q;
        b = v;
        break;
    case 4:
        r = t;
        g = p;
        b = v;
        break;
    case 5:
    default:
        r = v;
        g = p;
        b = q;
        break;
    }

    rgb = [	Math.round(r*255),
    		Math.round(g*255),
    		Math.round(b*255)];

    return rgb;
}
<|endoftext|>


<commit_before>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

var angular = angular || null;
var LOCK_EXPIRATION = 25;
var USER_IDLE_MINUTES = 15;

(function() {

    var module = angular.module('amara.SubtitleEditor.subtitles.directives', []);

    module.directive('subtitleEditor', function() {
        return function link(scope, elm, attrs) {
            // For some reason using ng-keydown at the HTML tag doesn't work.
            // Use jquery instead.
            $(document).keydown(function(evt) {
                scope.$apply(function(scope) {
                    scope.handleAppKeyDown(evt);
                });
            });
        };
    });
    module.directive('workingSubtitles', ['VideoPlayer', function(VideoPlayer) {
        return function link(scope, elem, attrs) {
            var startHelper = $('div.sync-help.begin', elem);
            var endHelper = $('div.sync-help.end', elem);
            var infoTray = $('div.info-tray', elem);
            var subtitleList = $('.subtitles ul', elem);
            var currentArrow = undefined;
            var wrapper = $(elem);

            function getSubtitleTop(index) {
                var li = $('li', subtitleList).eq(index);
                var top = li.offset().top - wrapper.offset().top;
                if(top < 0 || top + startHelper.height() >= wrapper.height()) {
                    return null;
                }
                return top;
            }

            var lastSyncStartIndex = null;
            var lastSyncEndIndex = null;

            scope.positionSyncHelpers = function(startIndex, endIndex) {
                if(startIndex === undefined) {
                    startIndex = lastSyncStartIndex;
                }
                if(endIndex === undefined) {
                    endIndex = lastSyncEndIndex;
                }
                lastSyncStartIndex = startIndex;
                lastSyncEndIndex = endIndex;

                if(!scope.timelineShown || !VideoPlayer.isPlaying()) {
                    startHelper.hide();
                    endHelper.hide();
                    return;
                }
                var startTop = null;
                var endTop = null;
                if(startIndex !== null) {
                    startTop = getSubtitleTop(startIndex);
                }
                if(endIndex !== null) {
                    endTop = getSubtitleTop(endIndex);
                }
                if(startTop !== null) {
                    startHelper.css('top', startTop + 'px');
                    startHelper.show();
                } else {
                    startHelper.hide();
                }
                if(endTop !== null) {
                    endHelper.css('top', endTop + 'px');
                    endHelper.show();
                } else {
                    endHelper.hide();
                }
            }

            scope.positionInfoTray = function() {
                var BUFFER = 22;
                var subtitle = scope.currentEdit.storedSubtitle();
                if(subtitle === null) {
                    infoTray.hide();
                    return;
                }
                var li = scope.getSubtitleRepeatItem(subtitle);
                if(li) {
                    currentArrow = li.find(".arrow");
                    var top = li.offset().top - wrapper.offset().top;
                    var bottom = top + li.height();

                    var infoTrayBottom = infoTray.height() + top;
                    var willTrim = wrapper.height() < infoTrayBottom;
                    if (willTrim) {
                        var adjustedTop = top - (infoTrayBottom - wrapper.height());
                        if (adjustedTop + BUFFER < bottom && adjustedTop - BUFFER > top - infoTray.height()) {
                            top = adjustedTop;
                        }
                    }
                    if(top + BUFFER >= 0 && top + BUFFER < wrapper.height()) {
                        infoTray.css('top', top + 'px');
                        infoTray.show();
                        currentArrow.show();
                    } else {
                        infoTray.hide();
                        currentArrow.hide();
                    }
                }
            }

            scope.$watch("currentEdit.draft", function() {
                if (currentArrow) {
                    currentArrow.hide(); // Kinda hate this, i think it would be cleaner to give each line its own controller
                }
                scope.positionInfoTray();
            });

            scope.$watch("timelineShown", function() {
                scope.positionSyncHelpers();
            });

            scope.$root.$on("video-playback-changes", function() {
                scope.positionSyncHelpers();
            });

            scope.$root.$on('working-subtitles-scrolled', function() {
                scope.positionSyncHelpers();
                scope.positionInfoTray();
            });
        };
    }]);

    module.directive('subtitleScroller', ["$window", function($window) {
        var window = $($window);
	var scrollingPrevious = [];
	$('div.subtitles').each(function(index) {
	    scrollingPrevious[index] = $(this).scrollTop();
	});
        return function link(scope, elem, attrs) {
            var scroller = $(elem);
            var isWorkingSet = (attrs.subtitleScroller == "working-subtitle-set");
            // Handle scroll.
            scroller.scroll(function() {
                // If scroll sync is locked.
                if (scope.scrollingSynced) {
                    var delta = 0;
                    var index_scrolled = -1;
                    $('div.subtitles').each(function(index) {
                        var newScrollTop = $(this).scrollTop();
                        if (newScrollTop != scrollingPrevious[index]) {
                            delta = newScrollTop - scrollingPrevious[index];
                            index_scrolled = index;
                        }
                    });
                    if (index_scrolled != -1) {
                        $('div.subtitles').each(function(index) {
                            if (index != index_scrolled) {
                                var newScrollTop = $(this).scrollTop() + delta;
                                $(this).scrollTop(newScrollTop);
                                var updatedScrollTop = $(this).scrollTop();
                                if ((updatedScrollTop != newScrollTop) && (updatedScrollTop != 0)) {
                                    $(this).children().last().height($(this).children().last().height() + newScrollTop - updatedScrollTop);
                                    $(this).scrollTop(newScrollTop);
                                }
                            }
                        });
                    }
                }
		$('div.subtitles').each(function(index) {
		    scrollingPrevious[index] = $(this).scrollTop();
		});

                if(isWorkingSet) {
                    scope.$root.$emit("working-subtitles-scrolled");
                }
            });

            function resizeScroller() {
                if (scope.timelineShown) {
                    var scrollerTop = 398;
                } else {
                    var scrollerTop = 327;
                }
                scroller.height(window.height() - scrollerTop);
            }

            scope.$watch('timelineShown', resizeScroller);
            window.on('resize', resizeScroller);
            resizeScroller();

            if (isWorkingSet) {
                scope.$root.$on('scroll-to-subtitle', function(evt, subtitle) {
                    if(scope.currentEdit.inProgress()) {
                        return;
                    }
                    var target = scope.getSubtitleRepeatItem(subtitle);
                    var prev = target.prev();
                    if(prev.length > 0) {
                        target = prev;
                    }
                    if(target) {
                        scroller.scrollTop(scroller.scrollTop() +
                                target.offset().top - scroller.offset().top);
                    }
                });
            }
        }
    }]);

    module.directive('subtitleRepeat', ["$interpolate", "$filter", "$parse", "DomUtil", "EditorData", function($interpolate, $filter, $parse,
                DomUtil, EditorData) {
        /* Specialized repeat directive to work with subtitleList
         *
         * Because we need to deal potentially thousands of subtitles,
         * ng-repeat is not ideal.  subtitleRepeat is a specialized directive
         * that creates the
 that we need for a subtitle list.  The
         * speedups come from a couple things:
         *
         *   - It hooks up to the change callback of subtitleList to calculate
         *   changes quickly.
         *   - it doesn't create a child scope for each subtitle.
         *
         *  When using subtitle-repeat, set the value of subtitle-repeat to
         *  the subtitleList to bind to.  Optionally, create a read-only
         *  attribute to specify a read-only list.
         */
        return function link($scope, elm, attrs) {
            var subtitleList = $scope.$eval(attrs.subtitleRepeat);
            // Map subtitle ID to DOM node for that subtitle
            var subtitleMap = {};
            // are we a read-only list?
            var readOnly = Boolean(attrs.readOnly);
            var displayTime = $filter('displayTime');
            // Template elements that we use to create list items
            var templateLI = $('
');
            templateLI.append('');
            templateLI.append('');
            if(!readOnly) {
		templateLI.append('!');
                templateLI.append(makeImageButton('remove-subtitle',
                    'images/editor/remove-subtitle.gif'));
                templateLI.append(makeImageButton('insert-subtitle',
                    'images/editor/plus.gif'));
                templateLI.append(
                        '<button class="new-paragraph">¶</button>');
            } else {
                templateLI.append('¶');
            }

            $scope.getSubtitleRepeatItem = function(subtitle) {
                var rv = subtitleMap[subtitle.id];
                if(rv !== undefined) {
                    return rv;
                } else {
                    return null;
                }
            }

            if(!readOnly) {
                $scope.$watch('currentEdit.draft', function(newValue, oldValue) {
                    if(oldValue) {
                        stopEditOn(oldValue);
                    }
                    if(newValue) {
                        startEditOn(newValue);
                    }
                });
                elm.on('click', function(evt) {
                    var action = findSubtitleClick(evt.target);
                    var subtitle = findSubtitleData(evt.target);
                    if(action && $scope.onSubtitleClick) {
                        $scope.$apply(function() {
                            $scope.onSubtitleClick(evt, subtitle, action);
                        });
                    }
                });
            }

            $scope.$watch('timeline.shownSubtitle', function(newSub, oldSub) {
                if(newSub && subtitleMap[newSub.id]) {
                    subtitleMap[newSub.id].addClass('current-subtitle');
                }
                if(oldSub && subtitleMap[oldSub.id]) {
                    subtitleMap[oldSub.id].removeClass('current-subtitle');
                }
            });

            subtitleList.addChangeCallback(onChange);
            reloadSubtitles();

            function makeImageButton(cssClass, imageURLPath) {
                var img = $('');
                img.prop('src', EditorData.staticURL + imageURLPath);
                var button = $('<button />');
                button.prop('class', cssClass);
                button.append(img);
                return button
            }

            function createLIForSubtitle(subtitle) {
                var elt = templateLI.clone();
                renderSubtitle(subtitle, elt);
                subtitleMap[subtitle.id] = elt;
                elt.data('subtitle', subtitle);
                return elt;
            }

            function renderSubtitle(subtitle, elt) {
                var content = subtitle.content();
                var classes = [];

                if($scope.timeline.shownSubtitle === subtitle) {
                    classes.push('current-subtitle');
                }
                if(content == '') {
                    classes.push('empty');
                }
                if(subtitle.startOfParagraph) {
                    classes.push('paragraph-start');
                }
                if($scope.currentEdit.isForSubtitle(subtitle)) {
                    classes.push('edit');
                }
                elt.prop('className', classes.join(' '));
                $('span.subtitle-text', elt).html(content);
                $('span.timing', elt).text(displayTime(subtitle.startTime));
		$('span.warning', elt).toggle($scope.warningsShown && subtitle.hasWarning());
            }

            function findSubtitleData(node) {
                // Find the subtitle that we set with the jquery data()
                // function by starting with node and moving up the DOM tree.
                var toplevelNode = elm[0];
                while(node && node != toplevelNode) {
                    var subtitle = $(node).data('subtitle');
                    if(subtitle) {
                        return subtitle;
                    } else {
                        node = node.parentNode;
                    }
                }
                return null;
            }

            function findSubtitleClick(node, dataName) {
                var toplevelNode = elm[0];
                while(node && node != toplevelNode) {
                    if(node.tagName == 'BUTTON') {
                        switch(node.className) {
                            case 'insert-subtitle':
                                return 'insert';

                            case 'remove-subtitle':
                                return 'remove';

                            case 'new-paragraph':
                                return 'changeParagraph'
                        }
                    } else if(node.tagName == 'LI') {
                        return 'edit';
                    }
                    node = node.parentNode;
                }
                return null;
            }

            function onChange(change) {
                var subtitle = change.subtitle;
                switch(change.type) {
                    case 'remove':
                        var node = subtitleMap[subtitle.id];
                        node.remove();
                        delete subtitleMap[subtitle.id];
                        break;
                    case 'update':
                        renderSubtitle(subtitle, subtitleMap[subtitle.id]);
                        break;
                    case 'insert':
                        if(change.before !== null) {
                            var node = subtitleMap[change.before.id];
                            node.before(createLIForSubtitle(subtitle));
                        } else {
                            elm.append(createLIForSubtitle(subtitle));
                        }
                        break;
                    case 'reload':
                        reloadSubtitles();
                }
            }

            function startEditOn(draft) {
                var li = subtitleMap[draft.storedSubtitle.id];
		if (li) {
                    li.addClass('edit');
                    var textarea = $('<textarea class="subtitle-edit" placeholder="Type a subtitle and press Enter"/>');
                    textarea.val(draft.markdown);
                    li.append(textarea);
                    textarea.autosize();
                    textarea.focus();
                    if(draft.initialCaretPos === undefined) {
                        var caretPos = draft.markdown.length;
                    } else {
                        var caretPos = draft.initialCaretPos;
                    }
                    DomUtil.setSelectionRange(textarea[0], caretPos, caretPos);
                    textarea.on('keyup input propertychange', function(evt) {
                        var val = textarea.val();
                        if(val != draft.markdown) {
                            $scope.$apply(function() {
                                draft.markdown = val;
                            });
                        }
                    });
                    if($scope.onEditKeydown) {
                        textarea.on('keydown', function(evt) {
                            $scope.$apply(function() {
                                $scope.onEditKeydown(evt);
                            });
                        });
                    }
                }
            }
            function stopEditOn(draft) {
                var li = subtitleMap[draft.storedSubtitle.id];
                if(li) {
                    li.removeClass('edit');
                    $('textarea.subtitle-edit', li).remove();
                    if ((draft.storedSubtitle.content().length == 0) && (subtitleMap[draft.storedSubtitle.id].next().html() == undefined)) {
                        subtitleList.removeSubtitle(draft.storedSubtitle);
                    }
                }
            }

            function reloadSubtitles() {
                elm.empty();
                subtitleMap = {}
                _.each(subtitleList.subtitles, function(subtitle) {
                    elm.append(createLIForSubtitle(subtitle));
                });
            }
        }
    }]);

    module.directive('languageSelector', ["SubtitleStorage", function(SubtitleStorage) {
        return {
            compile: function compile(elm, attrs, transclude) {
                return {
                    post: function post(scope, elm, attrs) {
                        /* For some reason, if we use ng-select angular
                         * creates an extra option because it thinks
                         * scope.versionNumber is an invalid value for the
                         * options, even though it isn't.  I think it has to
                         * do with the fact that we manually create options
                         * with ng-repeat.  In any case, we handle the
                         * select ourself.
                         */
                        $('select.version-select', elm).change(function(evt) {
                            scope.versionNumber = this.value;
                            scope.$digest();
                        });
                        scope.setInitialDisplayLanguage(SubtitleStorage.getLanguages());
                    }
                };
            }
        };
    }]);
})();
<commit_msg>Do not delete empty subtitle if it is the only one<commit_after>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

var angular = angular || null;
var LOCK_EXPIRATION = 25;
var USER_IDLE_MINUTES = 15;

(function() {

    var module = angular.module('amara.SubtitleEditor.subtitles.directives', []);

    module.directive('subtitleEditor', function() {
        return function link(scope, elm, attrs) {
            // For some reason using ng-keydown at the HTML tag doesn't work.
            // Use jquery instead.
            $(document).keydown(function(evt) {
                scope.$apply(function(scope) {
                    scope.handleAppKeyDown(evt);
                });
            });
        };
    });
    module.directive('workingSubtitles', ['VideoPlayer', function(VideoPlayer) {
        return function link(scope, elem, attrs) {
            var startHelper = $('div.sync-help.begin', elem);
            var endHelper = $('div.sync-help.end', elem);
            var infoTray = $('div.info-tray', elem);
            var subtitleList = $('.subtitles ul', elem);
            var currentArrow = undefined;
            var wrapper = $(elem);

            function getSubtitleTop(index) {
                var li = $('li', subtitleList).eq(index);
                var top = li.offset().top - wrapper.offset().top;
                if(top < 0 || top + startHelper.height() >= wrapper.height()) {
                    return null;
                }
                return top;
            }

            var lastSyncStartIndex = null;
            var lastSyncEndIndex = null;

            scope.positionSyncHelpers = function(startIndex, endIndex) {
                if(startIndex === undefined) {
                    startIndex = lastSyncStartIndex;
                }
                if(endIndex === undefined) {
                    endIndex = lastSyncEndIndex;
                }
                lastSyncStartIndex = startIndex;
                lastSyncEndIndex = endIndex;

                if(!scope.timelineShown || !VideoPlayer.isPlaying()) {
                    startHelper.hide();
                    endHelper.hide();
                    return;
                }
                var startTop = null;
                var endTop = null;
                if(startIndex !== null) {
                    startTop = getSubtitleTop(startIndex);
                }
                if(endIndex !== null) {
                    endTop = getSubtitleTop(endIndex);
                }
                if(startTop !== null) {
                    startHelper.css('top', startTop + 'px');
                    startHelper.show();
                } else {
                    startHelper.hide();
                }
                if(endTop !== null) {
                    endHelper.css('top', endTop + 'px');
                    endHelper.show();
                } else {
                    endHelper.hide();
                }
            }

            scope.positionInfoTray = function() {
                var BUFFER = 22;
                var subtitle = scope.currentEdit.storedSubtitle();
                if(subtitle === null) {
                    infoTray.hide();
                    return;
                }
                var li = scope.getSubtitleRepeatItem(subtitle);
                if(li) {
                    currentArrow = li.find(".arrow");
                    var top = li.offset().top - wrapper.offset().top;
                    var bottom = top + li.height();

                    var infoTrayBottom = infoTray.height() + top;
                    var willTrim = wrapper.height() < infoTrayBottom;
                    if (willTrim) {
                        var adjustedTop = top - (infoTrayBottom - wrapper.height());
                        if (adjustedTop + BUFFER < bottom && adjustedTop - BUFFER > top - infoTray.height()) {
                            top = adjustedTop;
                        }
                    }
                    if(top + BUFFER >= 0 && top + BUFFER < wrapper.height()) {
                        infoTray.css('top', top + 'px');
                        infoTray.show();
                        currentArrow.show();
                    } else {
                        infoTray.hide();
                        currentArrow.hide();
                    }
                }
            }

            scope.$watch("currentEdit.draft", function() {
                if (currentArrow) {
                    currentArrow.hide(); // Kinda hate this, i think it would be cleaner to give each line its own controller
                }
                scope.positionInfoTray();
            });

            scope.$watch("timelineShown", function() {
                scope.positionSyncHelpers();
            });

            scope.$root.$on("video-playback-changes", function() {
                scope.positionSyncHelpers();
            });

            scope.$root.$on('working-subtitles-scrolled', function() {
                scope.positionSyncHelpers();
                scope.positionInfoTray();
            });
        };
    }]);

    module.directive('subtitleScroller', ["$window", function($window) {
        var window = $($window);
	var scrollingPrevious = [];
	$('div.subtitles').each(function(index) {
	    scrollingPrevious[index] = $(this).scrollTop();
	});
        return function link(scope, elem, attrs) {
            var scroller = $(elem);
            var isWorkingSet = (attrs.subtitleScroller == "working-subtitle-set");
            // Handle scroll.
            scroller.scroll(function() {
                // If scroll sync is locked.
                if (scope.scrollingSynced) {
                    var delta = 0;
                    var index_scrolled = -1;
                    $('div.subtitles').each(function(index) {
                        var newScrollTop = $(this).scrollTop();
                        if (newScrollTop != scrollingPrevious[index]) {
                            delta = newScrollTop - scrollingPrevious[index];
                            index_scrolled = index;
                        }
                    });
                    if (index_scrolled != -1) {
                        $('div.subtitles').each(function(index) {
                            if (index != index_scrolled) {
                                var newScrollTop = $(this).scrollTop() + delta;
                                $(this).scrollTop(newScrollTop);
                                var updatedScrollTop = $(this).scrollTop();
                                if ((updatedScrollTop != newScrollTop) && (updatedScrollTop != 0)) {
                                    $(this).children().last().height($(this).children().last().height() + newScrollTop - updatedScrollTop);
                                    $(this).scrollTop(newScrollTop);
                                }
                            }
                        });
                    }
                }
		$('div.subtitles').each(function(index) {
		    scrollingPrevious[index] = $(this).scrollTop();
		});

                if(isWorkingSet) {
                    scope.$root.$emit("working-subtitles-scrolled");
                }
            });

            function resizeScroller() {
                if (scope.timelineShown) {
                    var scrollerTop = 398;
                } else {
                    var scrollerTop = 327;
                }
                scroller.height(window.height() - scrollerTop);
            }

            scope.$watch('timelineShown', resizeScroller);
            window.on('resize', resizeScroller);
            resizeScroller();

            if (isWorkingSet) {
                scope.$root.$on('scroll-to-subtitle', function(evt, subtitle) {
                    if(scope.currentEdit.inProgress()) {
                        return;
                    }
                    var target = scope.getSubtitleRepeatItem(subtitle);
                    var prev = target.prev();
                    if(prev.length > 0) {
                        target = prev;
                    }
                    if(target) {
                        scroller.scrollTop(scroller.scrollTop() +
                                target.offset().top - scroller.offset().top);
                    }
                });
            }
        }
    }]);

    module.directive('subtitleRepeat', ["$interpolate", "$filter", "$parse", "DomUtil", "EditorData", function($interpolate, $filter, $parse,
                DomUtil, EditorData) {
        /* Specialized repeat directive to work with subtitleList
         *
         * Because we need to deal potentially thousands of subtitles,
         * ng-repeat is not ideal.  subtitleRepeat is a specialized directive
         * that creates the
 that we need for a subtitle list.  The
         * speedups come from a couple things:
         *
         *   - It hooks up to the change callback of subtitleList to calculate
         *   changes quickly.
         *   - it doesn't create a child scope for each subtitle.
         *
         *  When using subtitle-repeat, set the value of subtitle-repeat to
         *  the subtitleList to bind to.  Optionally, create a read-only
         *  attribute to specify a read-only list.
         */
        return function link($scope, elm, attrs) {
            var subtitleList = $scope.$eval(attrs.subtitleRepeat);
            // Map subtitle ID to DOM node for that subtitle
            var subtitleMap = {};
            // are we a read-only list?
            var readOnly = Boolean(attrs.readOnly);
            var displayTime = $filter('displayTime');
            // Template elements that we use to create list items
            var templateLI = $('
');
            templateLI.append('');
            templateLI.append('');
            if(!readOnly) {
		templateLI.append('!');
                templateLI.append(makeImageButton('remove-subtitle',
                    'images/editor/remove-subtitle.gif'));
                templateLI.append(makeImageButton('insert-subtitle',
                    'images/editor/plus.gif'));
                templateLI.append(
                        '<button class="new-paragraph">¶</button>');
            } else {
                templateLI.append('¶');
            }

            $scope.getSubtitleRepeatItem = function(subtitle) {
                var rv = subtitleMap[subtitle.id];
                if(rv !== undefined) {
                    return rv;
                } else {
                    return null;
                }
            }

            if(!readOnly) {
                $scope.$watch('currentEdit.draft', function(newValue, oldValue) {
                    if(oldValue) {
                        stopEditOn(oldValue);
                    }
                    if(newValue) {
                        startEditOn(newValue);
                    }
                });
                elm.on('click', function(evt) {
                    var action = findSubtitleClick(evt.target);
                    var subtitle = findSubtitleData(evt.target);
                    if(action && $scope.onSubtitleClick) {
                        $scope.$apply(function() {
                            $scope.onSubtitleClick(evt, subtitle, action);
                        });
                    }
                });
            }

            $scope.$watch('timeline.shownSubtitle', function(newSub, oldSub) {
                if(newSub && subtitleMap[newSub.id]) {
                    subtitleMap[newSub.id].addClass('current-subtitle');
                }
                if(oldSub && subtitleMap[oldSub.id]) {
                    subtitleMap[oldSub.id].removeClass('current-subtitle');
                }
            });

            subtitleList.addChangeCallback(onChange);
            reloadSubtitles();

            function makeImageButton(cssClass, imageURLPath) {
                var img = $('');
                img.prop('src', EditorData.staticURL + imageURLPath);
                var button = $('<button />');
                button.prop('class', cssClass);
                button.append(img);
                return button
            }

            function createLIForSubtitle(subtitle) {
                var elt = templateLI.clone();
                renderSubtitle(subtitle, elt);
                subtitleMap[subtitle.id] = elt;
                elt.data('subtitle', subtitle);
                return elt;
            }

            function renderSubtitle(subtitle, elt) {
                var content = subtitle.content();
                var classes = [];

                if($scope.timeline.shownSubtitle === subtitle) {
                    classes.push('current-subtitle');
                }
                if(content == '') {
                    classes.push('empty');
                }
                if(subtitle.startOfParagraph) {
                    classes.push('paragraph-start');
                }
                if($scope.currentEdit.isForSubtitle(subtitle)) {
                    classes.push('edit');
                }
                elt.prop('className', classes.join(' '));
                $('span.subtitle-text', elt).html(content);
                $('span.timing', elt).text(displayTime(subtitle.startTime));
		$('span.warning', elt).toggle($scope.warningsShown && subtitle.hasWarning());
            }

            function findSubtitleData(node) {
                // Find the subtitle that we set with the jquery data()
                // function by starting with node and moving up the DOM tree.
                var toplevelNode = elm[0];
                while(node && node != toplevelNode) {
                    var subtitle = $(node).data('subtitle');
                    if(subtitle) {
                        return subtitle;
                    } else {
                        node = node.parentNode;
                    }
                }
                return null;
            }

            function findSubtitleClick(node, dataName) {
                var toplevelNode = elm[0];
                while(node && node != toplevelNode) {
                    if(node.tagName == 'BUTTON') {
                        switch(node.className) {
                            case 'insert-subtitle':
                                return 'insert';

                            case 'remove-subtitle':
                                return 'remove';

                            case 'new-paragraph':
                                return 'changeParagraph'
                        }
                    } else if(node.tagName == 'LI') {
                        return 'edit';
                    }
                    node = node.parentNode;
                }
                return null;
            }

            function onChange(change) {
                var subtitle = change.subtitle;
                switch(change.type) {
                    case 'remove':
                        var node = subtitleMap[subtitle.id];
                        node.remove();
                        delete subtitleMap[subtitle.id];
                        break;
                    case 'update':
                        renderSubtitle(subtitle, subtitleMap[subtitle.id]);
                        break;
                    case 'insert':
                        if(change.before !== null) {
                            var node = subtitleMap[change.before.id];
                            node.before(createLIForSubtitle(subtitle));
                        } else {
                            elm.append(createLIForSubtitle(subtitle));
                        }
                        break;
                    case 'reload':
                        reloadSubtitles();
                }
            }

            function startEditOn(draft) {
                var li = subtitleMap[draft.storedSubtitle.id];
		if (li) {
                    li.addClass('edit');
                    var textarea = $('<textarea class="subtitle-edit" placeholder="Type a subtitle and press Enter"/>');
                    textarea.val(draft.markdown);
                    li.append(textarea);
                    textarea.autosize();
                    textarea.focus();
                    if(draft.initialCaretPos === undefined) {
                        var caretPos = draft.markdown.length;
                    } else {
                        var caretPos = draft.initialCaretPos;
                    }
                    DomUtil.setSelectionRange(textarea[0], caretPos, caretPos);
                    textarea.on('keyup input propertychange', function(evt) {
                        var val = textarea.val();
                        if(val != draft.markdown) {
                            $scope.$apply(function() {
                                draft.markdown = val;
                            });
                        }
                    });
                    if($scope.onEditKeydown) {
                        textarea.on('keydown', function(evt) {
                            $scope.$apply(function() {
                                $scope.onEditKeydown(evt);
                            });
                        });
                    }
                }
            }
            function stopEditOn(draft) {
                var li = subtitleMap[draft.storedSubtitle.id];
                if(li) {
                    li.removeClass('edit');
                    $('textarea.subtitle-edit', li).remove();
                    if ((draft.storedSubtitle.content().length == 0) && (subtitleMap[draft.storedSubtitle.id].next().html() == undefined) && (subtitleList.length() > 1)) {
                        subtitleList.removeSubtitle(draft.storedSubtitle);
                    }
                }
            }

            function reloadSubtitles() {
                elm.empty();
                subtitleMap = {}
                _.each(subtitleList.subtitles, function(subtitle) {
                    elm.append(createLIForSubtitle(subtitle));
                });
            }
        }
    }]);

    module.directive('languageSelector', ["SubtitleStorage", function(SubtitleStorage) {
        return {
            compile: function compile(elm, attrs, transclude) {
                return {
                    post: function post(scope, elm, attrs) {
                        /* For some reason, if we use ng-select angular
                         * creates an extra option because it thinks
                         * scope.versionNumber is an invalid value for the
                         * options, even though it isn't.  I think it has to
                         * do with the fact that we manually create options
                         * with ng-repeat.  In any case, we handle the
                         * select ourself.
                         */
                        $('select.version-select', elm).change(function(evt) {
                            scope.versionNumber = this.value;
                            scope.$digest();
                        });
                        scope.setInitialDisplayLanguage(SubtitleStorage.getLanguages());
                    }
                };
            }
        };
    }]);
})();
<|endoftext|>


<commit_before>/* eslint-env node, jest */
// eslint-disable-next-line node/no-missing-require
const { environmentalize } = require('..').internal;

test('Should allow env-vars in the configuration which are resolved', () => {
  global._env = {
    FOOBAR: 'TEST',
  };

  const res = environmentalize(
    {
      foo: {
        bar: '[FOOBAR]',
        baz: '[SECOND]',
      },
    },
    { 'foo.bar': true }
  );

  delete global._env;
  expect(res._env.FOOBAR).toBe('TEST');
  expect(res._env.SECOND).toBe(undefined);
});

test('Should allow Object whitelisting', () => {
  global._env = {
    FOOBAR: 'TEST',
    SECOND: 'BANANA',
  };

  const res = environmentalize(
    {
      foo: {
        bar: '[FOOBAR]',
        baz: '[SECOND]',
      },
    },
    { foo: true }
  );

  delete global._env;
  expect(res._env.FOOBAR).toBe('TEST');
  expect(res._env.SECOND).toBe('BANANA');
});
<commit_msg>test(bootstrap): add more test-coverage<commit_after>/* eslint-env node, jest */
// eslint-disable-next-line node/no-missing-require
const { environmentalize } = require('..').internal;

test('Should allow env-vars in the configuration which are resolved', () => {
  global._env = {
    FOOBAR: 'TEST',
  };

  const res = environmentalize(
    {
      foo: {
        bar: '[FOOBAR]',
        baz: '[SECOND]',
      },
    },
    { 'foo.bar': true }
  );

  delete global._env;
  expect(res._env.FOOBAR).toBe('TEST');
  expect(res._env.SECOND).toBe(undefined);
});

test('Should allow Object whitelisting', () => {
  global._env = {
    FOOBAR: 'TEST',
    SECOND: 'BANANA',
  };

  const res = environmentalize(
    {
      foo: {
        bar: '[FOOBAR]',
        baz: '[SECOND]',
      },
    },
    { foo: true }
  );

  delete global._env;
  expect(res._env.FOOBAR).toBe('TEST');
  expect(res._env.SECOND).toBe('BANANA');
});

test('Should handle Arrays', () => {
  global._env = {
    FOOBAR: 'TEST',
    SECOND: 'PINEAPPLE',
  };

  const res = environmentalize(
    {
      foo: [
        {
          bar: '[FOOBAR]',
          baz: '[SECOND]',
        },
      ],
    },
    { foo: true }
  );

  delete global._env;
  expect(res._env.FOOBAR).toBe('TEST');
  expect(res._env.SECOND).toBe('PINEAPPLE');
});

test('Should support fallbacks', () => {
  global._env = {};

  const res = environmentalize(
    {
      foo: {
        bar: '[FOOBAR=apple]',
      },
    },
    { 'foo.bar': true }
  );

  delete global._env;
  expect(res._env.FOOBAR).toBe('apple');
  expect(res._env.SECOND).toBe(undefined);
});

test('Should ignore other props', () => {
  global._env = {
    FOOBAR: 'onion',
    SECOND: 'cheese',
  };

  const res = environmentalize(
    {
      foo: {
        bar: '[FOOBAR]',
      },
    },
    { 'foo.bar': true }
  );

  delete global._env;
  expect(res._env.FOOBAR).toBe('onion');
  expect(res._env.SECOND).toBe(undefined);
});
<|endoftext|>


<commit_before>"use strict";

require('../core/setup');

var _ = require('lodash'),
    $ = require('jquery'),
    assert = require('chai').assert,
    Marionette = require('../../shim/backbone.marionette'),
    coreModels = require('../core/models'),
    coreUtils = require('../core/utils'),
    models = require('./models'),
    views = require('./views'),
    mocks = require('./mocks'),
    App = require('../app'),
    testUtils = require('../core/testUtils');

var sandboxId = 'sandbox',
    sandboxSelector = '#' + sandboxId,
    SandboxRegion = Marionette.Region.extend({
        el: sandboxSelector
    });

function renderPtSrcAndWQTableRowValue(n) {
    if (n && !_.isNaN(n)) {
        return Number(n.toFixed(3)).toLocaleString('en',
            { minimumFractionDigits: 3 });
    } else {
        return coreUtils.noData;
    }
}

describe('Analyze', function() {
    before(function() {
        if ($(sandboxSelector).length === 0) {
            $('
', {id: sandboxId}).appendTo('body');
        }
    });

    beforeEach(function() {

    });

    afterEach(function() {
        $(sandboxSelector).remove();
        $('
', {id: sandboxId}).appendTo('body');
        testUtils.resetApp(App);
    });

    after(function() {
        $(sandboxSelector).remove();
    });

    describe('Views', function() {
        describe('AnalysisResultView', function() {
            _.each(['land', 'soil', 'animals', 'pointsource', 'catchment_water_quality'], testAnalysisType);
        });
    });
});

function testAnalysisType(type) {
    it('tests ' + type + ' analysis renders as expected', function(done) {
        var sandbox = new SandboxRegion(),
            result = new models.LayerModel(_.find(mocks.results, { name: type })),
            resultView = new views.AnalyzeResultViews[type]({
                model: result
            });

        resultView.listenTo(resultView, 'show', function() {
            var expectedTableHeaders = tableHeaders[type],
                expectedTableRows = tableRows(type, result),
                actualTableHeaders = $('table thead th').map(function() {
                    return $(this).text().trim();
                }).toArray(),
                actualTableRows = $('table tbody td').map(function() {
                    return $(this).text().trim();
                }).toArray();

            assert.deepEqual(expectedTableHeaders, actualTableHeaders);
            assert.deepEqual(expectedTableRows, actualTableRows);

            done();
        });

        sandbox.show(resultView);
    });
}

function landTableFormatter(categories) {
    var collection = new coreModels.LandUseCensusCollection(categories);

    return collection.map(function(category) {
        var name = category.get('type'),
            areaKm2 = coreUtils.changeOfAreaUnits(category.get('area'),
                                                  'm2',
                                                  'km2'),
            coverage = category.get('coverage') * 100;

        return [name, areaKm2.toFixed(2), coverage.toFixed(1)];
    });
}

function soilTableFormatter(categories) {
    var collection = new coreModels.SoilCensusCollection(categories);

    return collection.map(function(category) {
        var name = category.get('type'),
            areaKm2 = coreUtils.changeOfAreaUnits(category.get('area'),
                'm2',
                'km2'),
            coverage = category.get('coverage') * 100;

        return [name, areaKm2.toFixed(2), coverage.toFixed(1)];
    });
}

function animalTableFormatter(categories) {
    var collection = new coreModels.AnimalCensusCollection(categories);

    return collection.map(function(category) {
        var name = category.get('type'),
            aeu = category.get('aeu').toLocaleString();

        return [name, aeu];
    });
}

function pointsourceTableFormatter(categories) {
    var collection = new coreModels.PointSourceCensusCollection(categories);

    return collection.map(function(category) {
        var code = category.get('npdes_id'),
            city = coreUtils.toTitleCase(category.get('city')),
            discharge = renderPtSrcAndWQTableRowValue(category.get('mgd')),
            tn_load = renderPtSrcAndWQTableRowValue(category.get('kgn_yr')),
            tp_load = renderPtSrcAndWQTableRowValue(category.get('kgp_yr'));

        return [code, city, discharge, tn_load, tp_load];
    });
}

function catchmentWaterQualityTableFormatter(categories) {
    var collection = new coreModels.CatchmentWaterQualityCensusCollection(categories);
    return collection.map(function(category) {
        var nord = category.get('nord').toString(),
            areaha = renderPtSrcAndWQTableRowValue(category.get('areaha')),
            tn_tot_kgy = renderPtSrcAndWQTableRowValue(
                category.get('tn_tot_kgy')/category.get('areaha')),
            tp_tot_kgy = renderPtSrcAndWQTableRowValue(
                category.get('tp_tot_kgy')/category.get('areaha')),
            tss_tot_kg = renderPtSrcAndWQTableRowValue(
                category.get('tss_tot_kg')/category.get('areaha')),
            tn_yr_avg_ = renderPtSrcAndWQTableRowValue(category.get('tn_yr_avg_')),
            tp_yr_avg_ = renderPtSrcAndWQTableRowValue(category.get('tp_yr_avg_')),
            tss_concmg = renderPtSrcAndWQTableRowValue(category.get('tss_concmg'));

        return [nord, areaha, tn_tot_kgy, tp_tot_kgy, tss_tot_kg, tn_yr_avg_,
            tp_yr_avg_, tss_concmg];
    });
}

var dataFormatters = {
    land: landTableFormatter,
    soil: soilTableFormatter,
    animals: animalTableFormatter,
    pointsource: pointsourceTableFormatter,
    catchment_water_quality: catchmentWaterQualityTableFormatter,
};

var tableHeaders = {
    land: ['Type', 'Area (km2)', 'Coverage (%)'],
    soil: ['Type', 'Area (km2)', 'Coverage (%)'],
    animals: ['Animal', 'Count'],
    pointsource: ['NPDES Code', 'City', 'Discharge (MGD)', 'TN Load (kg/yr)', 'TP Load (kg/yr)'],
    catchment_water_quality: ['Id', 'Area (ha)', 'Total N (kg/ha)', 'Total P (kg/ha)',
        'Total TSS (kg/ha)', 'Avg TN (mg/l)', 'Avg TP (mg/l)', 'Avg TSS (mg/l)'],
};

function tableRows(type, result) {
    var formatter = dataFormatters[type];

    return _.flatten(formatter(result.get('categories')));
}
<commit_msg>Update tests<commit_after>"use strict";

require('../core/setup');

var _ = require('lodash'),
    $ = require('jquery'),
    assert = require('chai').assert,
    Marionette = require('../../shim/backbone.marionette'),
    coreModels = require('../core/models'),
    coreUtils = require('../core/utils'),
    models = require('./models'),
    views = require('./views'),
    mocks = require('./mocks'),
    App = require('../app'),
    testUtils = require('../core/testUtils');

var sandboxId = 'sandbox',
    sandboxSelector = '#' + sandboxId,
    SandboxRegion = Marionette.Region.extend({
        el: sandboxSelector
    });

function renderPtSrcAndWQTableRowValue(n) {
    if (n && !_.isNaN(n)) {
        return Number(n.toFixed(3)).toLocaleString('en',
            { minimumFractionDigits: 3 });
    } else {
        return coreUtils.noData;
    }
}

describe('Analyze', function() {
    before(function() {
        if ($(sandboxSelector).length === 0) {
            $('
', {id: sandboxId}).appendTo('body');
        }
    });

    beforeEach(function() {

    });

    afterEach(function() {
        $(sandboxSelector).remove();
        $('
', {id: sandboxId}).appendTo('body');
        testUtils.resetApp(App);
    });

    after(function() {
        $(sandboxSelector).remove();
    });

    describe('Views', function() {
        describe('AnalysisResultView', function() {
            _.each(['land', 'soil', 'animals', 'pointsource', 'catchment_water_quality'], testAnalysisType);
        });
    });
});

function testAnalysisType(type) {
    it('tests ' + type + ' analysis renders as expected', function(done) {
        var sandbox = new SandboxRegion(),
            result = new models.LayerModel(_.find(mocks.results, { name: type })),
            resultView = new views.AnalyzeResultViews[type]({
                model: result
            });

        resultView.listenTo(resultView, 'show', function() {
            var expectedTableHeaders = tableHeaders[type],
                expectedTableRows = tableRows(type, result),
                actualTableHeaders = $('table thead th').map(function() {
                    return $(this).text().trim();
                }).toArray(),
                actualTableRows = $('table tbody td').map(function() {
                    return $(this).text().trim();
                }).toArray();

            assert.deepEqual(expectedTableHeaders, actualTableHeaders);
            assert.deepEqual(expectedTableRows, actualTableRows);

            done();
        });

        sandbox.show(resultView);
    });
}

function landTableFormatter(categories) {
    var collection = new coreModels.LandUseCensusCollection(categories);

    return collection.map(function(category) {
        var name = category.get('type'),
            areaKm2 = coreUtils.changeOfAreaUnits(category.get('area'),
                                                  'm2',
                                                  'km2'),
            coverage = category.get('coverage') * 100;

        return [name, areaKm2.toFixed(2), coverage.toFixed(1)];
    });
}

function soilTableFormatter(categories) {
    var collection = new coreModels.SoilCensusCollection(categories);

    return collection.map(function(category) {
        var name = category.get('type'),
            areaKm2 = coreUtils.changeOfAreaUnits(category.get('area'),
                'm2',
                'km2'),
            coverage = category.get('coverage') * 100;

        return [name, areaKm2.toFixed(2), coverage.toFixed(1)];
    });
}

function animalTableFormatter(categories) {
    var collection = new coreModels.AnimalCensusCollection(categories);

    return collection.map(function(category) {
        var name = category.get('type'),
            aeu = category.get('aeu').toLocaleString();

        return [name, aeu];
    });
}

function pointsourceTableFormatter(categories) {
    var collection = new coreModels.PointSourceCensusCollection(categories);

    return collection.map(function(category) {
        var code = category.get('npdes_id'),
            city = coreUtils.toTitleCase(category.get('city')),
            discharge = renderPtSrcAndWQTableRowValue(category.get('mgd')),
            tn_load = renderPtSrcAndWQTableRowValue(category.get('kgn_yr')),
            tp_load = renderPtSrcAndWQTableRowValue(category.get('kgp_yr'));

        return [code, city, discharge, tn_load, tp_load];
    });
}

function catchmentWaterQualityTableFormatter(categories) {
    var collection = new coreModels.CatchmentWaterQualityCensusCollection(categories);
    return collection.map(function(category) {
        var nord = category.get('nord').toString(),
            areaha = renderPtSrcAndWQTableRowValue(category.get('areaha')),
            tn_tot_kgy = renderPtSrcAndWQTableRowValue(
                category.get('tn_tot_kgy')),
            tp_tot_kgy = renderPtSrcAndWQTableRowValue(
                category.get('tp_tot_kgy')),
            tss_tot_kg = renderPtSrcAndWQTableRowValue(
                category.get('tss_tot_kg')),
            tn_yr_avg_ = renderPtSrcAndWQTableRowValue(category.get('tn_yr_avg_')),
            tp_yr_avg_ = renderPtSrcAndWQTableRowValue(category.get('tp_yr_avg_')),
            tss_concmg = renderPtSrcAndWQTableRowValue(category.get('tss_concmg'));

        return [nord, areaha, tn_tot_kgy, tp_tot_kgy, tss_tot_kg, tn_yr_avg_,
            tp_yr_avg_, tss_concmg];
    });
}

var dataFormatters = {
    land: landTableFormatter,
    soil: soilTableFormatter,
    animals: animalTableFormatter,
    pointsource: pointsourceTableFormatter,
    catchment_water_quality: catchmentWaterQualityTableFormatter,
};

var tableHeaders = {
    land: ['Type', 'Area (km2)', 'Coverage (%)'],
    soil: ['Type', 'Area (km2)', 'Coverage (%)'],
    animals: ['Animal', 'Count'],
    pointsource: ['NPDES Code', 'City', 'Discharge (MGD)', 'TN Load (kg/yr)', 'TP Load (kg/yr)'],
    catchment_water_quality: ['Id', 'Area (ha)', 'Total N (kg/ha)', 'Total P (kg/ha)',
        'Total TSS (kg/ha)', 'Avg TN (mg/l)', 'Avg TP (mg/l)', 'Avg TSS (mg/l)'],
};

function tableRows(type, result) {
    var formatter = dataFormatters[type];

    return _.flatten(formatter(result.get('categories')));
}
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import {Helmet} from 'react-helmet'
import {html} from 'tocco-util'

import {AdminLink as StyledLink} from '../AdminLink'
import Icon from '../Icon'
import Typography from '../Typography'
import BreadcrumbSeparator from './BreadcrumbSeparator'
import {StyledBreadcrumbs, StyledBreadcrumbsLink, StyledBreadcrumbsTitle} from './StyledBreadcrumbs'

const getTitle = breadcrumbsInfo =>
  breadcrumbsInfo
    .slice(breadcrumbsInfo.length - 2)
    .map(breadcrumb => breadcrumb.title || breadcrumb.display)
    .reverse()
    .join(' - ')

const Breadcrumbs = ({pathPrefix, breadcrumbsInfo, currentView, backgroundColor, onClick, linkComp: LinkComp}) => {
  const breadcrumbs = [...(breadcrumbsInfo || []), ...(currentView ? [currentView] : [])]

  if (breadcrumbs.length === 0) {
    return null
  }

  const handleClick = breadcrumbsItem => () => {
    if (onClick) {
      onClick(breadcrumbsItem)
    }
  }

  return (

  )
}

Breadcrumbs.defaultProps = {
  pathPrefix: ''
}

Breadcrumbs.propTypes = {
  pathPrefix: PropTypes.string,
  match: PropTypes.object,
  breadcrumbsInfo: PropTypes.arrayOf(
    PropTypes.shape({
      display: PropTypes.string,
      title: PropTypes.string,
      path: PropTypes.string,
      type: PropTypes.string
    })
  ),
  currentView: PropTypes.shape({
    display: PropTypes.string,
    title: PropTypes.string
  }),
  backgroundColor: PropTypes.string,
  onClick: PropTypes.func,
  linkComp: PropTypes.any
}

export default Breadcrumbs
<commit_msg>refactor(tocco-ui): reverse title tag<commit_after>import PropTypes from 'prop-types'
import {Helmet} from 'react-helmet'
import {html} from 'tocco-util'

import {AdminLink as StyledLink} from '../AdminLink'
import Icon from '../Icon'
import Typography from '../Typography'
import BreadcrumbSeparator from './BreadcrumbSeparator'
import {StyledBreadcrumbs, StyledBreadcrumbsLink, StyledBreadcrumbsTitle} from './StyledBreadcrumbs'

const getTitle = breadcrumbsInfo =>
  breadcrumbsInfo
    .slice(breadcrumbsInfo.length - 2)
    .map(breadcrumb => breadcrumb.title || breadcrumb.display)
    .join(' - ')

const Breadcrumbs = ({pathPrefix, breadcrumbsInfo, currentView, backgroundColor, onClick, linkComp: LinkComp}) => {
  const breadcrumbs = [...(breadcrumbsInfo || []), ...(currentView ? [currentView] : [])]

  if (breadcrumbs.length === 0) {
    return null
  }

  const handleClick = breadcrumbsItem => () => {
    if (onClick) {
      onClick(breadcrumbsItem)
    }
  }

  const Breadcrumbs = breadcrumbs
    .map((b, idx) => {
      const display = b.display || ''
      const Breadcrumb = idx === breadcrumbs.length - 1 ? StyledBreadcrumbsTitle : StyledBreadcrumbsLink
      const activeProp = idx === breadcrumbs.length - 1 && {active: 'true'}
      const pathProp = typeof b.path !== 'undefined' ? {to: `${pathPrefix}/${b.path}`} : {}
      const componentType = LinkComp ? <LinkComp /> :
  )
}

Breadcrumbs.defaultProps = {
  pathPrefix: ''
}

Breadcrumbs.propTypes = {
  pathPrefix: PropTypes.string,
  match: PropTypes.object,
  breadcrumbsInfo: PropTypes.arrayOf(
    PropTypes.shape({
      display: PropTypes.string,
      title: PropTypes.string,
      path: PropTypes.string,
      type: PropTypes.string
    })
  ),
  currentView: PropTypes.shape({
    display: PropTypes.string,
    title: PropTypes.string
  }),
  backgroundColor: PropTypes.string,
  onClick: PropTypes.func,
  linkComp: PropTypes.any
}

export default Breadcrumbs
<|endoftext|>


<commit_before>
(function() {

cdb.admin.forms = {};

var polygon_stroke = {
   name: 'Polygon Stroke',
   form: {
      'line-width': { type: 'width', value: 1 },
      'line-color': { type: 'color' , value: '#FFF' },
      'line-opacity': { type: 'opacity', value: 1.0 }
   }
};

var line_stroke = {
   name: 'LineStroke',
   form: {
      'line-width': { type: 'width', value: 1 },
      'line-color': { type: 'color' , value: '#FF6600' },
      'line-opacity': { type: 'opacity', value: 0.7 }
   }
};

var line_stroke_chroloplet = {
   name: 'LineStroke',
   form: {
      'line-width': { type: 'width', value: 1 },
      'line-color': { type: 'hidden' , value: '#FF6600' },
      'line-opacity': { type: 'opacity', value: 0.8 }
   }
};

var polygon_fill = {
   name: 'Polygon Fill',
   form: {
      'polygon-fill': { type: 'color' , value: '#FF6600' },
      'polygon-opacity': { type: 'opacity' , value: 0.7 }
  }
};

var label_text = {
     name: 'Label Text',
     form: {
      'text-name': { type: 'select' }  /* value is filled by wizard */
     }
};

var label_text_properties = {
    name: 'Label Font',
    form: {
      'text-face-name': {
        type: 'select',
        value: "DejaVu Sans Book",
        extra: ["DejaVu Sans Book","unifont Medium"]
      },
      'text-size': { type: 'number', value: 10, min: 1, max:50 },
      'text-fill': { type: 'color' , value: '#000' },
      'text-allow-overlap': { type: 'hidden', value: true }
    }
};

var label_text_offset =  {
    name: 'Label Offset',
    form: {
      'text-dy': { type: 'number', value: -10, min: -30, max: 30 }
    }
}

var label_halo_properties = {
  name: 'Label Halo',
  form: {
    'text-halo-fill':  { type: 'color' , value: '#FFF' },
    'text-halo-radius': { type: 'number', value: 1, min: 0, max: 10, inc: 0.5}
  }
};

var marker_fill = {
       name: 'Marker Fill',
       form: {
          'marker-fill': { type: 'color' , value: '#FF6600' },
          'marker-opacity': { type: 'opacity' , value: 0.9 },
          'marker-allow-overlap': { type: 'hidden', value: true },
          'marker-placement':{ type:'hidden', value: 'point'},
          'marker-type':{ type:'hidden', value: 'ellipse'}
      }
};

var marker_radius = {
   name: 'Marker Width',
   form: {
      'marker-width': { type: 'width', value: 12 }
   }
};

var marker_stroke = {
   name: 'Marker Stroke',
   form: {
      'marker-line-width': { type: 'width', value: 3 },
      'marker-line-color': { type: 'color' , value: '#FFF' },
      'marker-line-opacity': { type: 'opacity', value: 0.9 }
  }
};

// from mapbox doc
//var comp_op_options = ['clear','src','dst','src-over','dst-over','src-in','dst-in','src-out','dst-out','src-atop','dst-atop','xor','plus','minus','multiply','screen','overlay','darken','lighten','color-dodge','color-burn','hard-light','soft-light','difference','exclusion','contrast','invert','invert-rgb','grain-merge','grain-extract','hue','saturation','color','value']
var comp_op_options = [ 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn' ];
var comp_op_default_value = 'src-over';

var polygon_comp_op= {
  name: 'Composite operation',
  form: {
    'polygon-comp-op': {
      type: 'select',
      value: comp_op_default_value,
      extra: comp_op_options
    }
  }
}

var line_comp_op= {
  name: 'Composite operation',
  form: {
    'line-comp-op': {
      type: 'select',
      value: comp_op_default_value,
      extra: comp_op_options
    }
  }
}

var marker_comp_op= {
  name: 'Composite operation',
  form: {
    'marker-comp-op': {
      type: 'select',
      value: comp_op_default_value,
      extra: comp_op_options
    }
  }
}

cdb.admin.forms.simple_form = {
  'polygon': [
    polygon_fill,
    polygon_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    polygon_comp_op
  ],
  'point': [
    marker_fill,
    marker_radius,
    marker_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    marker_comp_op
  ],
  'line': [
    line_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    line_comp_op
  ]
};

var column = {
  name: 'Column',
  form: { 'property': { type: 'select' } } /* value is filled by wizard */
};

var method = {
  name: 'Method',
  form: {
    // enable this when there is more than one method
    'stats_method': { type: 'select', value: 'Quantiles', extra: ['Quantiles'] }
  }
};

var buckets = {
  name: 'Buckets',
  form: {
    'method': { type: 'select', value: '7 Buckets', extra: ['3 Buckets', '5 Buckets', '7 Buckets'] }
  }
};

var color_ramp_polyline = {
  name: 'Color Ramp',
  form: {
    'color_ramp': { type: 'select', value: 'red', extra:['pink',
      'red', 'black', 'green', 'blue',
      'inverted_pink', 'inverted_red', 'inverted_black',
      'inverted_green', 'inverted_blue', 'spectrum1', 'spectrum2'] },
    'line-opacity': { type: 'opacity', value: 0.8 }
  }
};

var color_ramp_polygon = {
  name: 'Color Ramp',
  form: {
    'color_ramp': { type: 'select', value: 'red', extra:['pink',
      'red', 'black', 'green', 'blue',
      'inverted_pink', 'inverted_red', 'inverted_black',
      'inverted_green', 'inverted_blue', 'spectrum1', 'spectrum2'] },
    'polygon-opacity': { type: 'opacity', value: 0.8 }
  }
};

cdb.admin.forms.bubble_form = [
  column, // Select column
  {
     name: 'Radius',
     title: 'Radius (min-max)',
     form: {
       'radius_min': { type: 'number', value: 10, min:0, max:100 },
       'radius_max': { type: 'number', value: 25, min:0, max:100 }
     },
     text: '- to -'
  },
  {
     name: 'Bubble fill',
     form: {
       'marker-fill': { type: 'color', value: '#FF5C00' },
       'marker-opacity': { type: 'opacity', value: 0.9 }
     }
  },
  {
     name: 'Bubble stroke',
     form: {
       'marker-line-width': { type: 'number', value: 2, min:0, max:100, inc: 0.5 },
       'marker-line-color': { type: 'color', value: '#FFF' },
       'marker-line-opacity': { type: 'opacity', value: 1.0 }
     }
  },
  marker_comp_op
];

cdb.admin.forms.choropleth = {
  'polygon': [
    column,
    //method,
    buckets,
    color_ramp_polygon,
    polygon_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    polygon_comp_op

  ],

  'line': [
    column,
    //method,
    buckets,
    color_ramp_polyline,
    line_stroke_chroloplet,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    line_comp_op
  ],
  'point': [
    column,
    //method,
    buckets,
    color_ramp_polygon,
    marker_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    marker_comp_op
  ],
};

cdb.admin.forms.density = [
  {
     name: 'Method',
     form: {
       'geometry_type': { type: 'select', value: 'Hexagons', extra: ['Hexagons', 'Rectangles'] }
     }
  },
  {
     name: 'Buckets',
     form: {
       'method': { type: 'select', value: '5 Buckets', extra: ['5 Buckets', '7 Buckets'] }
     }
  },

  {
     name: 'Color ramp',
     form: {
       'color_ramp': { type: 'select', value: 'red', extra:['pink',
          'red', 'black', 'green', 'blue',
          'inverted_pink', 'inverted_red', 'inverted_black',
          'inverted_green', 'inverted_blue', 'spectrum1', 'spectrum2'] },
       'polygon-opacity': { type: 'opacity', value: 0.8 }
     }
  },

  polygon_stroke,
  {
    name: 'Polygon size',
    form: {
      'polygon-size': { type: 'number', value: 15, min: 1, max: 100 }
    }
  },

  polygon_comp_op
];

})();
<commit_msg>default line width to 2 #1358<commit_after>
(function() {

cdb.admin.forms = {};

var polygon_stroke = {
   name: 'Polygon Stroke',
   form: {
      'line-width': { type: 'width', value: 1 },
      'line-color': { type: 'color' , value: '#FFF' },
      'line-opacity': { type: 'opacity', value: 1.0 }
   }
};

var line_stroke = {
   name: 'LineStroke',
   form: {
      'line-width': { type: 'width', value: 2 },
      'line-color': { type: 'color' , value: '#FF6600' },
      'line-opacity': { type: 'opacity', value: 0.7 }
   }
};

var line_stroke_chroloplet = {
   name: 'LineStroke',
   form: {
      'line-width': { type: 'width', value: 1 },
      'line-color': { type: 'hidden' , value: '#FF6600' },
      'line-opacity': { type: 'opacity', value: 0.8 }
   }
};

var polygon_fill = {
   name: 'Polygon Fill',
   form: {
      'polygon-fill': { type: 'color' , value: '#FF6600' },
      'polygon-opacity': { type: 'opacity' , value: 0.7 }
  }
};

var label_text = {
     name: 'Label Text',
     form: {
      'text-name': { type: 'select' }  /* value is filled by wizard */
     }
};

var label_text_properties = {
    name: 'Label Font',
    form: {
      'text-face-name': {
        type: 'select',
        value: "DejaVu Sans Book",
        extra: ["DejaVu Sans Book","unifont Medium"]
      },
      'text-size': { type: 'number', value: 10, min: 1, max:50 },
      'text-fill': { type: 'color' , value: '#000' },
      'text-allow-overlap': { type: 'hidden', value: true }
    }
};

var label_text_offset =  {
    name: 'Label Offset',
    form: {
      'text-dy': { type: 'number', value: -10, min: -30, max: 30 }
    }
}

var label_halo_properties = {
  name: 'Label Halo',
  form: {
    'text-halo-fill':  { type: 'color' , value: '#FFF' },
    'text-halo-radius': { type: 'number', value: 1, min: 0, max: 10, inc: 0.5}
  }
};

var marker_fill = {
       name: 'Marker Fill',
       form: {
          'marker-fill': { type: 'color' , value: '#FF6600' },
          'marker-opacity': { type: 'opacity' , value: 0.9 },
          'marker-allow-overlap': { type: 'hidden', value: true },
          'marker-placement':{ type:'hidden', value: 'point'},
          'marker-type':{ type:'hidden', value: 'ellipse'}
      }
};

var marker_radius = {
   name: 'Marker Width',
   form: {
      'marker-width': { type: 'width', value: 12 }
   }
};

var marker_stroke = {
   name: 'Marker Stroke',
   form: {
      'marker-line-width': { type: 'width', value: 3 },
      'marker-line-color': { type: 'color' , value: '#FFF' },
      'marker-line-opacity': { type: 'opacity', value: 0.9 }
  }
};

// from mapbox doc
//var comp_op_options = ['clear','src','dst','src-over','dst-over','src-in','dst-in','src-out','dst-out','src-atop','dst-atop','xor','plus','minus','multiply','screen','overlay','darken','lighten','color-dodge','color-burn','hard-light','soft-light','difference','exclusion','contrast','invert','invert-rgb','grain-merge','grain-extract','hue','saturation','color','value']
var comp_op_options = [ 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn' ];
var comp_op_default_value = 'src-over';

var polygon_comp_op= {
  name: 'Composite operation',
  form: {
    'polygon-comp-op': {
      type: 'select',
      value: comp_op_default_value,
      extra: comp_op_options
    }
  }
}

var line_comp_op= {
  name: 'Composite operation',
  form: {
    'line-comp-op': {
      type: 'select',
      value: comp_op_default_value,
      extra: comp_op_options
    }
  }
}

var marker_comp_op= {
  name: 'Composite operation',
  form: {
    'marker-comp-op': {
      type: 'select',
      value: comp_op_default_value,
      extra: comp_op_options
    }
  }
}

cdb.admin.forms.simple_form = {
  'polygon': [
    polygon_fill,
    polygon_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    polygon_comp_op
  ],
  'point': [
    marker_fill,
    marker_radius,
    marker_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    marker_comp_op
  ],
  'line': [
    line_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    line_comp_op
  ]
};

var column = {
  name: 'Column',
  form: { 'property': { type: 'select' } } /* value is filled by wizard */
};

var method = {
  name: 'Method',
  form: {
    // enable this when there is more than one method
    'stats_method': { type: 'select', value: 'Quantiles', extra: ['Quantiles'] }
  }
};

var buckets = {
  name: 'Buckets',
  form: {
    'method': { type: 'select', value: '7 Buckets', extra: ['3 Buckets', '5 Buckets', '7 Buckets'] }
  }
};

var color_ramp_polyline = {
  name: 'Color Ramp',
  form: {
    'color_ramp': { type: 'select', value: 'red', extra:['pink',
      'red', 'black', 'green', 'blue',
      'inverted_pink', 'inverted_red', 'inverted_black',
      'inverted_green', 'inverted_blue', 'spectrum1', 'spectrum2'] },
    'line-opacity': { type: 'opacity', value: 0.8 }
  }
};

var color_ramp_polygon = {
  name: 'Color Ramp',
  form: {
    'color_ramp': { type: 'select', value: 'red', extra:['pink',
      'red', 'black', 'green', 'blue',
      'inverted_pink', 'inverted_red', 'inverted_black',
      'inverted_green', 'inverted_blue', 'spectrum1', 'spectrum2'] },
    'polygon-opacity': { type: 'opacity', value: 0.8 }
  }
};

cdb.admin.forms.bubble_form = [
  column, // Select column
  {
     name: 'Radius',
     title: 'Radius (min-max)',
     form: {
       'radius_min': { type: 'number', value: 10, min:0, max:100 },
       'radius_max': { type: 'number', value: 25, min:0, max:100 }
     },
     text: '- to -'
  },
  {
     name: 'Bubble fill',
     form: {
       'marker-fill': { type: 'color', value: '#FF5C00' },
       'marker-opacity': { type: 'opacity', value: 0.9 }
     }
  },
  {
     name: 'Bubble stroke',
     form: {
       'marker-line-width': { type: 'number', value: 2, min:0, max:100, inc: 0.5 },
       'marker-line-color': { type: 'color', value: '#FFF' },
       'marker-line-opacity': { type: 'opacity', value: 1.0 }
     }
  },
  marker_comp_op
];

cdb.admin.forms.choropleth = {
  'polygon': [
    column,
    //method,
    buckets,
    color_ramp_polygon,
    polygon_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    polygon_comp_op

  ],

  'line': [
    column,
    //method,
    buckets,
    color_ramp_polyline,
    line_stroke_chroloplet,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    line_comp_op
  ],
  'point': [
    column,
    //method,
    buckets,
    color_ramp_polygon,
    marker_stroke,
    label_text,
    label_text_properties,
    label_halo_properties,
    label_text_offset,
    marker_comp_op
  ],
};

cdb.admin.forms.density = [
  {
     name: 'Method',
     form: {
       'geometry_type': { type: 'select', value: 'Hexagons', extra: ['Hexagons', 'Rectangles'] }
     }
  },
  {
     name: 'Buckets',
     form: {
       'method': { type: 'select', value: '5 Buckets', extra: ['5 Buckets', '7 Buckets'] }
     }
  },

  {
     name: 'Color ramp',
     form: {
       'color_ramp': { type: 'select', value: 'red', extra:['pink',
          'red', 'black', 'green', 'blue',
          'inverted_pink', 'inverted_red', 'inverted_black',
          'inverted_green', 'inverted_blue', 'spectrum1', 'spectrum2'] },
       'polygon-opacity': { type: 'opacity', value: 0.8 }
     }
  },

  polygon_stroke,
  {
    name: 'Polygon size',
    form: {
      'polygon-size': { type: 'number', value: 15, min: 1, max: 100 }
    }
  },

  polygon_comp_op
];

})();
<|endoftext|>


<commit_before>/*
 * This plugin parses externs/oli.js as well as goog.exportSymbol and
 * goog.exportProperty calls to build a list of API symbols and properties.
 * Unexported modules linked from @param or @fires will be marked unexported,
 * and the documentation will not contain the constructor. Everything else is
 * marked undocumented, which will remove it from the docs.
 */

var api = [];
var unexported = [];

function collectExports(source) {
  var i, ii, symbol, property;
  var syms = source.match(/goog\.exportSymbol\([^\)]*\)/g);
  if (syms) {
    i = 0; ii = syms.length;
    for (; i < ii; ++i) {
      symbol = syms[i].match(/'([^']*)'/)[1];
      api.push(symbol);
    }
  }
  var props = source.match(/goog\.exportProperty\([^\)]*\)/g);
  if (props) {
    i = 0; ii = props.length;
    for (; i < ii; ++i) {
      property = props[i].match(/[^,]*,[^,]*,\r?\n? *([^\)]*)\)/)[1]
          .replace('.prototype.', '#');
      api.push(property);
    }
  }
}

function collectOliExports(source) {
  var oli = source.match(/[^\{]oli\.([^;^ ]*);? ?/g);
  if (oli) {
    i = 0; ii = oli.length;
    for (; i < ii; ++i) {
      property = 'ol.' + oli[i].match(/oli.([^;]*)/)[1]
          .replace('.prototype.', '#');
      unexported.push(property);
    }
  }
}

var encoding = env.conf.encoding || 'utf8';
var fs = require('jsdoc/fs');
collectExports(fs.readFileSync('build/exports.js', encoding));
collectOliExports(fs.readFileSync('externs/oli.js', encoding));

exports.handlers = {

  beforeParse: function(e) {
    if (/\.js$/.test(e.filename)) {
      collectExports(e.source);
    }
  },

  newDoclet: function(e) {
    var i, ii, j, jj;
    if (e.doclet.meta.filename == "olx.js" && e.doclet.longname != 'olx') {
      api.push(e.doclet.longname);
    }
    if (api.indexOf(e.doclet.longname) > -1) {
      var names, name;
      var params = e.doclet.params;
      if (params) {
        for (i = 0, ii = params.length; i < ii; ++i) {
          names = params[i].type.names;
          if (names) {
            for (j = 0, jj=names.length; j < jj; ++j) {
              name = names[j];
              if (unexported.indexOf(name) === -1) {
                unexported.push(name);
              }
            }
          }
        }
      }
      var links = e.doclet.comment.match(/\{@link ([^\}]*)\}/g);
      if (links) {
        for (i=0, ii=links.length; i < ii; ++i) {
          var link = links[i].match(/\{@link (.*)\}/)[1];
          if (unexported.indexOf(link) === -1) {
            unexported.push(link);
          }
        }
      }
    }
  },

  parseComplete: function(e) {
    for (var j = e.doclets.length - 1; j >= 0; --j) {
      var doclet = e.doclets[j];
      if (doclet.meta.filename == 'olx.js' && doclet.kind == 'typedef') {
        for (var i = e.doclets.length - 1; i >= 0; --i) {
          var propertyDoclet = e.doclets[i];
          if (propertyDoclet.memberof == doclet.longname) {
            if (!doclet.properties) {
              doclet.properties = [];
            }
            doclet.properties.unshift(propertyDoclet);
            e.doclets.splice(i, 1)
          }
        }
      }
      if (doclet.kind == 'namespace' || doclet.kind == 'event' || doclet.fires) {
        continue;
      }
      var fqn = doclet.longname;
      if (fqn) {
        doclet.unexported = (api.indexOf(fqn) === -1 && unexported.indexOf(fqn) !== -1);
        if (api.indexOf(fqn) === -1 && unexported.indexOf(fqn) === -1) {
          e.doclets.splice(j, 1);
        }
      }
    }
  }

};
<commit_msg>Use symbols.json for determining the API<commit_after>/*
 * This plugin parses externs/oli.js as well as goog.exportSymbol and
 * goog.exportProperty calls to build a list of API symbols and properties.
 * Unexported modules linked from @param or @fires will be marked unexported,
 * and the documentation will not contain the constructor. Everything else is
 * marked undocumented, which will remove it from the docs.
 */

var api = [];
var unexported = [];
var observablesByClass = {};

function collectExports(source) {
  var symbols = JSON.parse(source).symbols;
  for (var i = 0, ii = symbols.length; i < ii; ++i) {
    api.push(symbols[i].name);
  }
}

function collectOliExports(source) {
  var oli = source.match(/[^\{]oli\.([^;^ ]*);? ?/g);
  if (oli) {
    i = 0; ii = oli.length;
    for (; i < ii; ++i) {
      property = 'ol.' + oli[i].match(/oli.([^;]*)/)[1]
          .replace('.prototype.', '#');
      unexported.push(property);
    }
  }
}

var encoding = env.conf.encoding || 'utf8';
var fs = require('jsdoc/fs');
collectExports(fs.readFileSync('build/symbols.json', encoding));
collectOliExports(fs.readFileSync('externs/oli.js', encoding));

exports.handlers = {

  newDoclet: function(e) {
    var i, ii, j, jj;
    if (e.doclet.meta.filename == "olx.js" && e.doclet.longname != 'olx') {
      api.push(e.doclet.longname);
    }
    if (api.indexOf(e.doclet.longname) > -1) {
      var names, name;
      var params = e.doclet.params;
      if (params) {
        for (i = 0, ii = params.length; i < ii; ++i) {
          names = params[i].type.names;
          if (names) {
            for (j = 0, jj=names.length; j < jj; ++j) {
              name = names[j];
              if (unexported.indexOf(name) === -1) {
                unexported.push(name);
              }
            }
          }
        }
      }
      var links = e.doclet.comment.match(/\{@link ([^\}]*)\}/g);
      if (links) {
        for (i=0, ii=links.length; i < ii; ++i) {
          var link = links[i].match(/\{@link (.*)\}/)[1];
          if (unexported.indexOf(link) === -1) {
            unexported.push(link);
          }
        }
      }
    }
    if (e.doclet.observables) {
      var observables = observablesByClass[e.doclet.longname] = [];
      for (i = e.doclet.observables.length - 1; i >= 0; --i) {
         observables.push(e.doclet.observables[i].name);
      }
    }
  },

  parseComplete: function(e) {
    for (var j = e.doclets.length - 1; j >= 0; --j) {
      var doclet = e.doclets[j];
      if (doclet.meta.filename == 'olx.js' && doclet.kind == 'typedef') {
        for (var i = e.doclets.length - 1; i >= 0; --i) {
          var propertyDoclet = e.doclets[i];
          if (propertyDoclet.memberof == doclet.longname) {
            if (!doclet.properties) {
              doclet.properties = [];
            }
            doclet.properties.unshift(propertyDoclet);
            e.doclets.splice(i, 1)
          }
        }
      }
      if (doclet.kind == 'namespace' || doclet.kind == 'event' || doclet.fires) {
        continue;
      }
      var fqn = doclet.longname;
      if (fqn) {
        var getterOrSetter = fqn.match(/([^#]*)#[gs]et(.*)/);
        if (getterOrSetter) {
          var observables = observablesByClass[getterOrSetter[1]];
          if (observables && observables.indexOf(getterOrSetter[2].toLowerCase()) > -1) {
            // Always document getters/setters of observables
            continue;
          }
        }
        doclet.unexported = (api.indexOf(fqn) === -1 && unexported.indexOf(fqn) !== -1);
        if (api.indexOf(fqn) === -1 && unexported.indexOf(fqn) === -1) {
          e.doclets.splice(j, 1);
        }
      }
    }
  }

};
<|endoftext|>