<commit_before>(function(root) {
    var Map = {},
        layers;

    Map = function(el, l, callback) {
        var map = this;
        wax.tilejson(l.api, function(t) {
            var handlers = [
                new MM.DragHandler(),
                new MM.DoubleClickHandler(),
                new MM.TouchHandler()
            ];
            if ($.inArray('zoomwheel', l.features) >= 0) {
                handlers.push(new MM.MouseWheelHandler());
            }
            if ($.inArray('static', l.features) >= 0) {
                handlers = null;
            }

            MM_map = new MM.Map(el, new wax.mm.connector(t), null, handlers);
            setCenterOffset(MM_map, {
                lat: (l.center) ? l.center.lat : t.center[1],
                lon: (l.center) ? l.center.lon : t.center[0]
            }, (l.center) ? l.center.zoom : t.center[2]);

            if (l.zoomRange) {
                MM_map.setZoomRange(l.zoomRange[0], l.zoomRange[1]);
            } else {
                MM_map.setZoomRange(t.minzoom, t.maxzoom);
            }

            wax.mm.attribution(MM_map, t).appendTo(MM_map.parent);

            for (var i = 0; i < l.features.length; i++) {
                switch(l.features[i]) {
                    case 'zoompan':
                        wax.mm.zoomer(MM_map).appendTo(MM_map.parent);
                        break;
                    case 'zoombox':
                        wax.mm.zoombox(MM_map);
                        break;
                    case 'legend':
                        MM_map.legend = wax.mm.legend(MM_map, t).appendTo(MM_map.parent);
                        break;
                    case 'bwdetect':
                        wax.mm.bwdetect(MM_map);
                        break;
                    case 'share':
                        wax.mm.share(MM_map, t).appendTo($('body')[0]);
                        break;
                    case 'tooltips':
                        MM_map.interaction = wax.mm.interaction()
                            .map(MM_map)
                            .tilejson(t)
                            .on(wax.tooltip()
                                .parent(MM_map.parent)
                                .events()
                            );
                        break;
                    case 'movetips':
                        MM_map.interaction = wax.mm.interaction()
                            .map(MM_map)
                            .tilejson(t)
                            .on(wax.movetip()
                                .parent(MM_map.parent)
                                .events()
                            );
                        break;
                }
            }
            Map.bootstrap(l);
            picks.start(t.data[0]);
            if (callback && typeof(callback) == 'function') callback();
        });
        return Map;
    };

    Map.layers = function(x) {
        if (!arguments.length) return layers;
        layers = x;
        return Map;
    };

    Map.layerGroups = [];

    Map.setOverlay = function(id) {

        if (id) {
            if (!layers[id]) throw new Error('overlay with id ' + id + ' not found');
            var l = layers[id];

            l.group = l.group || 0;

            if (l.api) {
                Map.layerGroups[l.group] = {
                    id: id,
                    api: l.api
                };
            }
        }

        if ((l && l.api) || (!l && Map.layerGroups.length > 0)) {
            var compositedLayers = function() {
                var layerIds = [];
                $.each(Map.layerGroups, function(index, layer) {
                    if (layer && layer.api) {
                        layerIds.push(layer.api.match(/v\d\/(.*?).jsonp/)[1]);
                        $('[href="#' + layer.id + '"]').addClass('active');
                    }
                });
                return 'http://a.tiles.mapbox.com/v3/' + layerIds.join(',') + '.jsonp';
            };

            wax.tilejson(compositedLayers(), function(t) {
                var level = (l && l.level === 'base') ? 0 : 1;

                try {
                    MM_map.setLayerAt(level, new wax.mm.connector(t));
                } catch (e) {
                    MM_map.insertLayerAt(level, new wax.mm.connector(t));
                }
                if (MM_map.interaction) MM_map.interaction.map(MM_map).tilejson(t);
                if (MM_map.legend) {
                    MM_map.legend.content(t);
                }
            });
        }

        if (l && l.center) {
            var lat = l.center.lat || MM_map.getCenter().lat,
                lon = l.center.lon || MM_map.getCenter().lon,
                zoom = l.center.zoom || MM_map.getZoom();

            if (l.center.ease > 0) {
                MM_map.easey = easey().map(MM_map)
                    .to(MM_map.locationCoordinate({ lat: lat, lon: lon })
                    .zoomTo(zoom)).run(l.center.ease);
            } else {
                setCenterOffset(MM_map, { lat: lat, lon: lon }, zoom);
            }
        }
    };

    Map.removeOverlay = function(id) {

        if (!layers[id]) throw new Error('overlay with id ' + id + ' not found');
        var l = layers[id];

        l.group = l.group || 0;
        delete Map.layerGroups[l.group];

        if (cleanArray(Map.layerGroups).length > 0) {
            Map.setOverlay();
        } else {
            var level = (l.level === 'base') ? 0 : 1;
            MM_map.removeLayerAt(level);
            if (MM_map.legend) MM_map.legend.content(' ');
            if (MM_map.interaction) MM_map.interaction.remove();
        }
    };

    Map.parseHash = function() {
        var pattern = /(?:#([^\?]*))?(?:\?(.*))?$/,
            components = window.location.href.match(pattern);

        if (components && components[2] === 'embed') {
            $('body').removeClass().addClass('embed');
            window.location.replace(window.location.href.split('?')[0]);
        }

        if (components && components[1]) {
            var hash = components[1];
            if (hash.substr(0, 1) === '/') hash = hash.substring(1);
            if (hash.substr(hash.length - 1, 1) === '/') hash = hash.substr(0, hash.length - 1);

            ids = decodeURIComponent(hash).split('/');

            $.each(ids, function(i, layer) {
                if (layer !== '-' && layers[layer]) {
                    Map.layerGroups[i] = {
                        id: layer,
                        api: layers[layer].api
                    };
                }
            });
        }
        if (cleanArray(Map.layerGroups).length > 0) {
            Map.setOverlay();
        } else {
            if (layers) {
                $.each(layers, function(key, layer) {
                    if (layer.initial) Map.setOverlay(key);
                });
            }
        }
    };

    Map.setHash = function() {
        var hash = [];

        $.each(Map.layerGroups, function(index, layer) {
            var id = (layer && layer.id) ? layer.id : '-';
            hash.push(encodeURIComponent(id));
        });

        var l = window.location,
            baseUrl = l.href,
            state = (hash.length > 0) ? '#/' + hash.join('/') : '';

        if (baseUrl.indexOf('?') >= 0) baseUrl = baseUrl.split('?')[0];
        if (baseUrl.indexOf('#') >= 0) baseUrl = baseUrl.split('#')[0];

        l.replace(baseUrl + state);

        // Update share urls
        var webpage = l.href;
        var embed = (l.hash) ? l.href + '?embed' : l.href + '#/?embed';

        $('.wax-share textarea.embed').val(
            ''
            .replace('{embed}', embed));
        $('.wax-share a.twitter').attr('href', 'http://twitter.com/intent/tweet?status='
            + encodeURIComponent(document.title + ' (' + webpage + ')'));
        $('.wax-share a.facebook').attr('href', 'https://www.facebook.com/sharer.php?u='
            + encodeURIComponent(webpage) + '&t=' + encodeURIComponent(document.title));
    };

    root.Map = Map;

})(this);

// Bind the geocoder functionality to any div with the format
//
//

//        <form class="geocode">
//          <input placeholder='Search for an address' type="text">
//          <input type='submit' />
//

//        </form>
//

//
function bindGeocoder() {
    $('[data-control="geocode"] form').submit(function(e) {
        var m = $('[data-control="geocode"]').attr('data-map');
        // If this doesn't explicitly name the layer it should affect,
        // use the first layer in MB.maps
        e.preventDefault();
        geocode($('input[type=text]', this).val(), m);
    });
    var geocode = function(query, m) {
        query = encodeURIComponent(query);
        $('form.geocode').addClass('loading');
        reqwest({
            url: 'http://open.mapquestapi.com/nominatim/v1/search?format=json&json_callback=callback&&limit=1&q=' + query,
            type: 'jsonp',
            jsonpCallback: 'callback',
            success: function (r) {
                r = r[0];

                if (MM_map.geocodeLayer) {
                    MM_map.geocodeLayer.removeAllMarkers();
                }

                $('form.geocode').removeClass('loading');

                if (r === undefined) {
                    $('#geocode-error').text('This address cannot be found.').fadeIn('fast');
                } else {
                    $('#geocode-error').hide();
                    MM_map.setExtent([
                        { lat: r.boundingbox[1], lon: r.boundingbox[2] },
                        { lat: r.boundingbox[0], lon: r.boundingbox[3] }
                    ]);

                    if (MM_map.getZoom() === MM_map.coordLimits[1].zoom) {
                        var point = { 'type': 'FeatureCollection',
                            'features': [{ 'type': 'Feature',
                            'geometry': { 'type': 'Point','coordinates': [r.lon, r.lat] },
                            'properties': {}
                        }]};

                        if (MM_map.geocodeLayer) {
                            MM_map.geocodeLayer.removeAllMarkers();
                            MM_map.geocodeLayer.geojson(point);
                        } else {
                            MM_map.geocodeLayer = mmg()
                                .geojson(point);
                            MM_map.addLayer(MM_map.geocodeLayer);
                        }

                        setCenterOffset(MM_map, { lat: r.lat, lon: r.lon });
                    }
                }
            }
        });
        var attribution = 'Search by MapQuest Open';
        if ($('.wax-attribution').html().indexOf(attribution) < 0) {
            $('.wax-attribution').append(' - ' + attribution);
        }
    };
}

function cleanArray(actual){
    var newArray = new Array();
    for(var i = 0; i<actual.length; i++){
        if (actual[i]){
            newArray.push(actual[i]);
        }
    }
    return newArray;
}

Map.bootstrap = function(l) {

    $('body').on('click.map', '[data-control="layer"]', function(e) {
        var $this = $(this),
            id = $this.attr('href');
        id = id.replace(/.*(?=#[^\s]+$)/, '').slice(1);
        var m = $('[data-control="geocode"]').attr('data-map') || 'main';
        e.preventDefault();
        if($this.hasClass('active')) {
            if(Map.layers()[id].toggle) {
                $('[data-control="layer"]').removeClass('active');
                window[m].removeOverlay(id);
            }
        } else {
            $('[data-control="layer"]').removeClass('active');
            window[m].setOverlay(id);
        }
        if ($.inArray('layerHash', l.features) >= 0) Map.setHash();
    });

    bindGeocoder();
    Map.parseHash();
};

// Set center on map while offsetting to compensate for drawer if open.
var setCenterOffset = function(map, location, zoom) {
    if (zoom)
        map.setZoom(zoom);
    var drawerWidth = 320;
    var point = map.locationPoint(location);
    point.x += drawerWidth / 2;
    location = map.pointLocation(point);
    map.setCenter(location);
}
<commit_msg>Fix #14<commit_after>(function(root) {
    var Map = {},
        layers;

    Map = function(el, l, callback) {
        var map = this;
        wax.tilejson(l.api, function(t) {
            var handlers = [
                new MM.DragHandler(),
                new MM.DoubleClickHandler(),
                new MM.TouchHandler()
            ];
            if ($.inArray('zoomwheel', l.features) >= 0) {
                handlers.push(new MM.MouseWheelHandler());
            }
            if ($.inArray('static', l.features) >= 0) {
                handlers = null;
            }

            MM_map = new MM.Map(el, new wax.mm.connector(t), null, handlers);
            setCenterOffset(MM_map, {
                lat: (l.center) ? l.center.lat : t.center[1],
                lon: (l.center) ? l.center.lon : t.center[0]
            }, (l.center) ? l.center.zoom : t.center[2]);

            if (l.zoomRange) {
                MM_map.setZoomRange(l.zoomRange[0], l.zoomRange[1]);
            } else {
                MM_map.setZoomRange(t.minzoom, t.maxzoom);
            }

            wax.mm.attribution(MM_map, t).appendTo(MM_map.parent);

            for (var i = 0; i < l.features.length; i++) {
                switch(l.features[i]) {
                    case 'zoompan':
                        wax.mm.zoomer(MM_map).appendTo(MM_map.parent);
                        break;
                    case 'zoombox':
                        wax.mm.zoombox(MM_map);
                        break;
                    case 'legend':
                        MM_map.legend = wax.mm.legend(MM_map, t).appendTo(MM_map.parent);
                        break;
                    case 'bwdetect':
                        wax.mm.bwdetect(MM_map);
                        break;
                    case 'share':
                        wax.mm.share(MM_map, t).appendTo($('body')[0]);
                        break;
                    case 'tooltips':
                        MM_map.interaction = wax.mm.interaction()
                            .map(MM_map)
                            .tilejson(t)
                            .on(wax.tooltip()
                                .parent(MM_map.parent)
                                .events()
                            );
                        break;
                    case 'movetips':
                        MM_map.interaction = wax.mm.interaction()
                            .map(MM_map)
                            .tilejson(t)
                            .on(wax.movetip()
                                .parent(MM_map.parent)
                                .events()
                            );
                        break;
                }
            }
            Map.bootstrap(l);
            picks.start(t.data[0]);
            if (callback && typeof(callback) == 'function') callback();
        });
        return Map;
    };

    Map.layers = function(x) {
        if (!arguments.length) return layers;
        layers = x;
        return Map;
    };

    Map.layerGroups = [];

    Map.setOverlay = function(id) {

        if (id) {
            if (!layers[id]) throw new Error('overlay with id ' + id + ' not found');
            var l = layers[id];

            l.group = l.group || 0;

            if (l.api) {
                Map.layerGroups[l.group] = {
                    id: id,
                    api: l.api
                };
            }
        }

        if ((l && l.api) || (!l && Map.layerGroups.length > 0)) {
            var compositedLayers = function() {
                var layerIds = [];
                $.each(Map.layerGroups, function(index, layer) {
                    if (layer && layer.api) {
                        layerIds.push(layer.api.match(/v\d\/(.*?).jsonp/)[1]);
                        $('[href="#' + layer.id + '"]').addClass('active');
                    }
                });
                return 'http://a.tiles.mapbox.com/v3/' + layerIds.join(',') + '.jsonp';
            };

            wax.tilejson(compositedLayers(), function(t) {
                var level = (l && l.level === 'base') ? 0 : 1;

                try {
                    MM_map.setLayerAt(level, new wax.mm.connector(t));
                } catch (e) {
                    MM_map.insertLayerAt(level, new wax.mm.connector(t));
                }
                if (MM_map.interaction) MM_map.interaction.map(MM_map).tilejson(t);
                if (MM_map.legend) {
                    MM_map.legend.content(t);
                }
            });
        }

        if (l && l.center) {
            var lat = l.center.lat || MM_map.getCenter().lat,
                lon = l.center.lon || MM_map.getCenter().lon,
                zoom = l.center.zoom || MM_map.getZoom();

            if (l.center.ease > 0) {
                MM_map.easey = easey().map(MM_map)
                    .to(MM_map.locationCoordinate({ lat: lat, lon: lon })
                    .zoomTo(zoom)).run(l.center.ease);
            } else {
                setCenterOffset(MM_map, { lat: lat, lon: lon }, zoom);
            }
        }
    };

    Map.removeOverlay = function(id) {

        if (!layers[id]) throw new Error('overlay with id ' + id + ' not found');
        var l = layers[id];

        l.group = l.group || 0;
        delete Map.layerGroups[l.group];

        if (cleanArray(Map.layerGroups).length > 0) {
            Map.setOverlay();
        } else {
            var level = (l.level === 'base') ? 0 : 1;
            MM_map.removeLayerAt(level);
            if (MM_map.legend) MM_map.legend.content(' ');
            if (MM_map.interaction) MM_map.interaction.remove();
        }
    };

    Map.parseHash = function() {
        var pattern = /(?:#([^\?]*))?(?:\?(.*))?$/,
            components = window.location.href.match(pattern);

        if (components && components[2] === 'embed') {
            $('body').removeClass().addClass('embed');
            window.location.replace(window.location.href.split('?')[0]);
        }

        if (components && components[1]) {
            var hash = components[1];
            if (hash.substr(0, 1) === '/') hash = hash.substring(1);
            if (hash.substr(hash.length - 1, 1) === '/') hash = hash.substr(0, hash.length - 1);

            ids = decodeURIComponent(hash).split('/');

            $.each(ids, function(i, layer) {
                if (layer !== '-' && layers[layer]) {
                    Map.layerGroups[i] = {
                        id: layer,
                        api: layers[layer].api
                    };
                }
            });
        }
        if (cleanArray(Map.layerGroups).length > 0) {
            Map.setOverlay();
        } else {
            if (layers) {
                $.each(layers, function(key, layer) {
                    if (layer.initial) Map.setOverlay(key);
                });
            }
        }
    };

    Map.setHash = function() {
        var hash = [];

        $.each(Map.layerGroups, function(index, layer) {
            var id = (layer && layer.id) ? layer.id : '-';
            hash.push(encodeURIComponent(id));
        });

        var l = window.location,
            baseUrl = l.href,
            state = (hash.length > 0) ? '#/' + hash.join('/') : '';

        if (baseUrl.indexOf('?') >= 0) baseUrl = baseUrl.split('?')[0];
        if (baseUrl.indexOf('#') >= 0) baseUrl = baseUrl.split('#')[0];

        l.replace(baseUrl + state);

        // Update share urls
        var webpage = l.href;
        var embed = (l.hash) ? l.href + '?embed' : l.href + '#/?embed';

        $('.wax-share textarea.embed').val(
            ''
            .replace('{embed}', embed));
        $('.wax-share a.twitter').attr('href', 'http://twitter.com/intent/tweet?status='
            + encodeURIComponent(document.title + ' (' + webpage + ')'));
        $('.wax-share a.facebook').attr('href', 'https://www.facebook.com/sharer.php?u='
            + encodeURIComponent(webpage) + '&t=' + encodeURIComponent(document.title));
    };

    root.Map = Map;

})(this);

// Bind the geocoder functionality to any div with the format
//
//

//        <form class="geocode">
//          <input placeholder='Search for an address' type="text">
//          <input type='submit' />
//

//        </form>
//

//
function bindGeocoder() {
    $('[data-control="geocode"] form').submit(function(e) {
        var m = $('[data-control="geocode"]').attr('data-map');
        // If this doesn't explicitly name the layer it should affect,
        // use the first layer in MB.maps
        e.preventDefault();
        geocode($('input[type=text]', this).val(), m);
    });
    var geocode = function(query, m) {
        query = encodeURIComponent(query);
        $('form.geocode').addClass('loading');
        reqwest({
            url: 'http://open.mapquestapi.com/nominatim/v1/search?format=json&json_callback=callback&&limit=1&q=' + query,
            type: 'jsonp',
            jsonpCallback: 'callback',
            success: function (r) {
                r = r[0];

                if (MM_map.geocodeLayer) {
                    MM_map.geocodeLayer.removeAllMarkers();
                }

                $('form.geocode').removeClass('loading');

                if (r === undefined) {
                    $('#geocode-error').text('This address cannot be found.').fadeIn('fast');
                } else {
                    $('#geocode-error').hide();
                    MM_map.setExtent([
                        { lat: r.boundingbox[1], lon: r.boundingbox[2] },
                        { lat: r.boundingbox[0], lon: r.boundingbox[3] }
                    ]);

                    if (MM_map.getZoom() === MM_map.coordLimits[1].zoom) {
                        var point = { 'type': 'FeatureCollection',
                            'features': [{ 'type': 'Feature',
                            'geometry': { 'type': 'Point','coordinates': [r.lon, r.lat] },
                            'properties': {}
                        }]};

                        if (MM_map.geocodeLayer) {
                            MM_map.geocodeLayer.removeAllMarkers();
                            MM_map.geocodeLayer.features(point);
                        } else {
                            MM_map.geocodeLayer = mmg()
                                .features(point);
                            MM_map.addLayer(MM_map.geocodeLayer);
                        }

                        setCenterOffset(MM_map, { lat: r.lat, lon: r.lon });
                    }
                }
            }
        });
        var attribution = 'Search by MapQuest Open';
        if ($('.wax-attribution').html().indexOf(attribution) < 0) {
            $('.wax-attribution').append(' - ' + attribution);
        }
    };
}

function cleanArray(actual){
    var newArray = new Array();
    for(var i = 0; i<actual.length; i++){
        if (actual[i]){
            newArray.push(actual[i]);
        }
    }
    return newArray;
}

Map.bootstrap = function(l) {

    $('body').on('click.map', '[data-control="layer"]', function(e) {
        var $this = $(this),
            id = $this.attr('href');
        id = id.replace(/.*(?=#[^\s]+$)/, '').slice(1);
        var m = $('[data-control="geocode"]').attr('data-map') || 'main';
        e.preventDefault();
        if($this.hasClass('active')) {
            if(Map.layers()[id].toggle) {
                $('[data-control="layer"]').removeClass('active');
                window[m].removeOverlay(id);
            }
        } else {
            $('[data-control="layer"]').removeClass('active');
            window[m].setOverlay(id);
        }
        if ($.inArray('layerHash', l.features) >= 0) Map.setHash();
    });

    bindGeocoder();
    Map.parseHash();
};

// Set center on map while offsetting to compensate for drawer if open.
var setCenterOffset = function(map, location, zoom) {
    if (zoom)
        map.setZoom(zoom);
    var drawerWidth = 320;
    var point = map.locationPoint(location);
    point.x += drawerWidth / 2;
    location = map.pointLocation(point);
    map.setCenter(location);
}
<|endoftext|>


<commit_before>import fetch_jsonp from 'fetch-jsonp';
import { createSelector } from 'reselect';

export const FRAME_RATE = 44100;
export const frame_to_time = (frame) => frame / FRAME_RATE;
export const time_to_frame = (time)  => time  * FRAME_RATE;

const ACTION_HANDLERS = {
    set_subs: (state, action) => ({
        ...state,
        subs: action.subs,
    }),
    toggle_play: (state, action) => {
        const should_play = !state.is_playing;
        if (should_play) {
            action.audio.play();
        }
        else {
            action.audio.pause();
        }
        return {
            ...state,
            is_playing: !state.is_playing,
        };
    },
    set_audio_metadata: (state, action) => {
        return {
            ...state,
            frame_cnt: time_to_frame(action.audio.duration),
        };
    },
    sync_current_frame: (state, action) => {
        return {
            ...state,
            current_frame: action.current_frame,
            subs_txt:      action.subs_txt,
        };
    },
    force_current_frame: (state, action) => {
        return {
            ...state,
            current_frame: action.current_frame,
        };
    },
};

const initial_state = {
    subs: [],
    frame_cnt: 0,
    current_frame: 0,
    is_playing: false,
};

export const init = (store, stem) => {
    fetch_jsonp(
        API_BASE + '/static/subs/' + stem + '.sub.js', {
            timeout:               30000,
            jsonpCallback:         'jsonp_subtitles',
            jsonpCallbackFunction: 'jsonp_subtitles',
        }
    )
    .then((res) => res.json())
    .then((sub_data) => {
        calculate_word_positions(sub_data.data);
        store.dispatch({
            type: 'set_subs',
            subs: sub_data.data,
        });
    });
};

function calculate_word_positions(subs) {
    var pos = 0;
    subs.forEach((word) => {
        word.position = pos;
        pos += word.occurrence.length + 1;
    });
}

const get_subs         = (state) => state.track_detail.subs;
const get_current_time = (state) => frame_to_time(state.track_detail.current_frame);
const get_subs_txt     = (state) => state.track_detail.subs_txt;
export const get_subs_str = createSelector(
    [get_subs],
    (subs) => subs.map((sub) => sub.occurrence).join(' '),
);
export const get_word_timestamps = createSelector(
    [get_subs],
    (subs) => subs.map((sub, i) => sub.timestamp).concat(Infinity),
);
let current_word;
const range = document.createRange();
//const sel   = document.getSelection();
export const get_current_word = createSelector(
    [get_word_timestamps, get_current_time, get_subs, get_subs_txt],
    (word_timestamps, current_time, subs, subs_txt) => {
        if (subs.length === 0) {
            return {
                occurrence: '',
                rects: [],
                start_offset: null,
                end_offset: null,
            };
        }
        let i = current_word ? current_word.i : 0;
        while (word_timestamps[i+1] <= current_time) i++;
        while (word_timestamps[i  ] >  current_time) i--;
        const sub = subs[i];
        let start_offset = null;
        let end_offset   = null;
        let rects = [];
        if (sub && subs_txt && current_word) {
            start_offset = sub.position;
            end_offset   = sub.position+sub.occurrence.length;
            range.setStart(subs_txt, current_word.start_offset);
            range.setEnd  (subs_txt, current_word.end_offset  );
            //sel.removeAllRanges();
            //sel.addRange(range);
            rects = range.getClientRects();
        }
        return current_word = {
            i,
            start_offset: sub?sub.position:null,
            end_offset: sub?sub.position+sub.occurrence.length:null,
            rects,
            ...sub,
        };
    },
);

export function toggle_play(audio) {
    return {
        type: 'toggle_play',
        audio,
    };
};
export function set_audio_metadata(audio) {
    return {
        type: 'set_audio_metadata',
        audio,
    };
};
export function sync_current_frame(audio, subs_txt) {
    return {
        type: 'sync_current_frame',
        current_frame: audio.currentTime * 44100,
        audio,
        subs_txt,
    };
};
export function force_current_frame(current_frame, audio) {
    audio.currentTime = frame_to_time(current_frame);
    return {
        type: 'force_current_frame',
        current_frame,
        audio,
    };
};

export default function reducer (state = initial_state, action) {
    const handler = ACTION_HANDLERS[action.type];
    return handler ? handler(state, action) : state;
};
<commit_msg>more compact reducers<commit_after>import fetch_jsonp from 'fetch-jsonp';
import { createSelector } from 'reselect';

export const FRAME_RATE = 44100;
export const frame_to_time = (frame) => frame / FRAME_RATE;
export const time_to_frame = (time)  => time  * FRAME_RATE;

const ACTION_HANDLERS = {
    set_subs: (state, action) => ({
        ...state,
        subs: action.subs,
    }),
    toggle_play: (state, action) => {
        const should_play = !state.is_playing;
        if (should_play) {
            action.audio.play();
        }
        else {
            action.audio.pause();
        }
        return {
            ...state,
            is_playing: !state.is_playing,
        };
    },
    set_audio_metadata: (state, action) => ({
        ...state,
        frame_cnt: time_to_frame(action.audio.duration),
    }),
    sync_current_frame: (state, action) => ({
        ...state,
        current_frame: action.current_frame,
        subs_txt:      action.subs_txt,
    }),
    force_current_frame: (state, action) => ({
        ...state,
        current_frame: action.current_frame,
    }),
};

const initial_state = {
    subs: [],
    frame_cnt: 0,
    current_frame: 0,
    is_playing: false,
};

export const init = (store, stem) => {
    fetch_jsonp(
        API_BASE + '/static/subs/' + stem + '.sub.js', {
            timeout:               30000,
            jsonpCallback:         'jsonp_subtitles',
            jsonpCallbackFunction: 'jsonp_subtitles',
        }
    )
    .then((res) => res.json())
    .then((sub_data) => {
        calculate_word_positions(sub_data.data);
        store.dispatch({
            type: 'set_subs',
            subs: sub_data.data,
        });
    });
};

function calculate_word_positions(subs) {
    var pos = 0;
    subs.forEach((word) => {
        word.position = pos;
        pos += word.occurrence.length + 1;
    });
}

const get_subs         = (state) => state.track_detail.subs;
const get_current_time = (state) => frame_to_time(state.track_detail.current_frame);
const get_subs_txt     = (state) => state.track_detail.subs_txt;
export const get_subs_str = createSelector(
    [get_subs],
    (subs) => subs.map((sub) => sub.occurrence).join(' '),
);
export const get_word_timestamps = createSelector(
    [get_subs],
    (subs) => subs.map((sub, i) => sub.timestamp).concat(Infinity),
);
let current_word;
const range = document.createRange();
//const sel   = document.getSelection();
export const get_current_word = createSelector(
    [get_word_timestamps, get_current_time, get_subs, get_subs_txt],
    (word_timestamps, current_time, subs, subs_txt) => {
        if (subs.length === 0) {
            return {
                occurrence: '',
                rects: [],
                start_offset: null,
                end_offset: null,
            };
        }
        let i = current_word ? current_word.i : 0;
        while (word_timestamps[i+1] <= current_time) i++;
        while (word_timestamps[i  ] >  current_time) i--;
        const sub = subs[i];
        let start_offset = null;
        let end_offset   = null;
        let rects = [];
        if (sub && subs_txt && current_word) {
            start_offset = sub.position;
            end_offset   = sub.position+sub.occurrence.length;
            range.setStart(subs_txt, current_word.start_offset);
            range.setEnd  (subs_txt, current_word.end_offset  );
            //sel.removeAllRanges();
            //sel.addRange(range);
            rects = range.getClientRects();
        }
        return current_word = {
            i,
            start_offset: sub?sub.position:null,
            end_offset: sub?sub.position+sub.occurrence.length:null,
            rects,
            ...sub,
        };
    },
);

export function toggle_play(audio) {
    return {
        type: 'toggle_play',
        audio,
    };
};
export function set_audio_metadata(audio) {
    return {
        type: 'set_audio_metadata',
        audio,
    };
};
export function sync_current_frame(audio, subs_txt) {
    return {
        type: 'sync_current_frame',
        current_frame: audio.currentTime * 44100,
        audio,
        subs_txt,
    };
};
export function force_current_frame(current_frame, audio) {
    audio.currentTime = frame_to_time(current_frame);
    return {
        type: 'force_current_frame',
        current_frame,
        audio,
    };
};

export default function reducer (state = initial_state, action) {
    const handler = ACTION_HANDLERS[action.type];
    return handler ? handler(state, action) : state;
};
<|endoftext|>


<commit_before>var gulp = require('gulp');
var gutil = require('gulp-util');
var peg = require('gulp-peg');
var jshint = require('gulp-jshint');
var mocha = require('gulp-mocha');
var replace = require('gulp-replace');

// make gulp work from subdirectories
process.chdir(__dirname);

var options = {
  peg: {
    'allowedStartRules': [
      'SIP_message'
      ,'encoding'
    ]
  },
  jshint: {
    laxcomma: true,
    laxbreak: true,
    '-W100': true
  },
  mocha: {
    reporter: 'spec'
  }
};

gulp.task('build', function() {
  return gulp
    .src('src/sippers.pegjs')
    .pipe(peg(options.peg).on('error', gutil.log))
    // modify generated parse method
    .pipe(replace('parse:       parse',
      'parse: function (input, options){' +
        // allow startRule to be passed as string, default to 'SIP_message'
        'options = options || "SIP_message";' +
        'if (options.constructor === String) {' +
          'options = {startRule: options};' +
        '}' +
        // RFC 3261 25.1:
        // All linear white space, including folding, has the same semantics as SP.
        'var emptyLine = "\\r\\n\\r\\n";' +
        'var hadEmptyLine = input.indexOf(emptyLine) > -1;' +
        'var folding = /[\\t ]*\\r\\n[\\t ]+/g;' +
        'var headersBody = input.split(emptyLine, 2);' +
        'var headers = headersBody[0].replace(folding, " ");' +
        'var body = headersBody[1] || "";' +
        'var result = headers + (hadEmptyLine ? emptyLine : "") + body;' +
        'return parse(result, options);' +
      '}'))
    .pipe(gulp.dest('dist'))
  ;
});

gulp.task('lint', ['build'], function () {
  return gulp
    .src('dist/sippers.js')
    .pipe(jshint(options.jshint))
    .pipe(jshint.reporter('default'))
  ;
});

gulp.task('test', ['lint'], function () {
  return gulp
    .src('test/**/index.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(mocha(options.mocha))
  ;
});

gulp.task('default', ['test']);
<commit_msg>parse: move LWS folding to foldLWS method<commit_after>var gulp = require('gulp');
var gutil = require('gulp-util');
var peg = require('gulp-peg');
var jshint = require('gulp-jshint');
var mocha = require('gulp-mocha');
var replace = require('gulp-replace');

// make gulp work from subdirectories
process.chdir(__dirname);

var options = {
  peg: {
    'allowedStartRules': [
      'SIP_message'
      ,'encoding'
    ]
  },
  jshint: {
    laxcomma: true,
    laxbreak: true,
    '-W100': true
  },
  mocha: {
    reporter: 'spec'
  }
};

gulp.task('build', function() {
  return gulp
    .src('src/sippers.pegjs')
    .pipe(peg(options.peg).on('error', gutil.log))
    // modify generated parse method
    .pipe(replace('parse:       parse',
      'parse: function (input, options){' +
        // allow startRule to be passed as string, default to 'SIP_message'
        'options = options || "SIP_message";' +
        'if (options.constructor === String) {' +
          'options = {startRule: options};' +
        '}' +
        'return parse(this.foldLWS(input), options);' +
      '},' +
      'foldLWS: function foldLWS (input) {' +
        // RFC 3261 25.1:
        // All linear white space, including folding, has the same semantics as SP.
        'var emptyLine = "\\r\\n\\r\\n";' +
        'var hadEmptyLine = input.indexOf(emptyLine) > -1;' +
        'var folding = /[\\t ]*\\r\\n[\\t ]+/g;' +
        'var headersBody = input.split(emptyLine, 2);' +
        'var headers = headersBody[0].replace(folding, " ");' +
        'var body = headersBody[1] || "";' +
        'return headers + (hadEmptyLine ? emptyLine : "") + body;' +
      '}'))
    .pipe(gulp.dest('dist'))
  ;
});

gulp.task('lint', ['build'], function () {
  return gulp
    .src('dist/sippers.js')
    .pipe(jshint(options.jshint))
    .pipe(jshint.reporter('default'))
  ;
});

gulp.task('test', ['lint'], function () {
  return gulp
    .src('test/**/index.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(mocha(options.mocha))
  ;
});

gulp.task('default', ['test']);
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 App = require('app');
var blueprintUtils = require('utils/blueprint');

App.EnhancedConfigsMixin = Em.Mixin.create(App.ConfigWithOverrideRecommendationParser, {

  /**
   * this value is used for observing
   * whether recommendations for dependent properties was received from server
   * @type {number}
   */
  recommendationTimeStamp: null,

  /**
   * this property is used to force update min/max values
   * for not default config groups
   * @type {boolean}
   */
  forceUpdateBoundaries: false,

  /**
   * object with loaded recommendations
   *
   * @type {Object}
   */
  recommendationsConfigs: null,

  /**
   * flag is true when Ambari changes some of the dependent properties
   * @type {boolean}
   */
  hasChangedDependencies: Em.computed.and('isControllerSupportsEnhancedConfigs', 'changedProperties.length'),

  /**
   * defines is block with changed dependent configs should be shown
   * rely on controller
   * @type {boolean}
   */
  isControllerSupportsEnhancedConfigs: Em.computed.existsIn('name', ['wizardStep7Controller','mainServiceInfoConfigsController']),

  dependenciesGroupMessage: Em.I18n.t('popup.dependent.configs.dependencies.for.groups'),
  /**
   * message fro alert box for dependent configs
   * @type {string}
   */
  dependenciesMessage: function() {
    var changedProperties = this.get('changedProperties').filterProperty('saveRecommended');
    var changedServices = changedProperties.mapProperty('serviceName').uniq();
    var cfgLenSuffix = changedProperties.length === 1 ? 'singular' : 'plural';
    var sLenSuffix = changedServices.length === 1 ? 'singular' : 'plural';
    return Em.I18n.t('popup.dependent.configs.dependencies.config.' + cfgLenSuffix).format(changedProperties.length)
      + Em.I18n.t('popup.dependent.configs.dependencies.service.' + sLenSuffix).format(changedServices.length);
  }.property('changedProperties.length'),

  /**
   * dependent properties that was changed by Ambari
   * @type {Object[]}
   */
  changedProperties: function() {
    return this.get('recommendations').filter(function(dp) {
      return (this.get('selectedConfigGroup.isDefault') && Em.get(dp, 'configGroup').contains('Default'))
        || [this.get('selectedConfigGroup.name'), this.get('selectedConfigGroup.dependentConfigGroups') && this.get('selectedConfigGroup.dependentConfigGroups')[Em.get(dp, 'serviceName')]].contains(Em.get(dp, 'configGroup'));
    }, this);
  }.property('recommendations.@each.saveRecommended', 'recommendations.@each.recommendedValue', 'selectedConfigGroup'),

  /**
   * defines if change dependent group message should be shown
   * @type {boolean}
   */
  showSelectGroupsPopup: Em.computed.and('!selectedConfigGroup.isDefault', 'selectedService.dependentServiceNames.length'),

  /**
   * set default values for dependentGroups
   * @method setDependentGroups
   */
  setDependentGroups: function () {
    if (this.get('selectedConfigGroup') && this.get('isControllerSupportsEnhancedConfigs') && !this.get('selectedConfigGroup.isDefault') && this.get('selectedService.dependentServiceNames.length')) {
      this.get('selectedService.dependentServiceNames').forEach(function (serviceName) {
        if (!this.get('selectedConfigGroup.dependentConfigGroups')[serviceName]) {
          var stepConfig = this.get('stepConfigs').findProperty('serviceName', serviceName);
          if (stepConfig) {
            stepConfig.get('configGroups').filterProperty('isDefault', false).forEach(function (configGroup) {
              this.get('selectedService.configGroups').filterProperty('isDefault', false).forEach(function (currentServiceGroup) {
                if (currentServiceGroup.get('dependentConfigGroups')[serviceName] != configGroup.get('name')) {
                  var dependentGroups = $.extend({},this.get('selectedConfigGroup.dependentConfigGroups'));
                  dependentGroups[serviceName] = configGroup.get('name');
                  this.set('selectedConfigGroup.dependentConfigGroups', dependentGroups);
                }
              }, this);
            }, this);
          }
        }
      }, this);
    }
  }.observes('selectedConfigGroup'),

  /******************************METHODS THAT WORKS WITH DEPENDENT CONFIGS *************************************/

  /**
   * get config group object for current service
   * @param serviceName
   * @returns {App.ConfigGroup|null}
   */
  getGroupForService: function(serviceName) {
    if (!this.get('stepConfigs') || this.get('stepConfigs.length') === 0) {
      return null;
    }
    if (this.get('selectedService.serviceName') === serviceName) {
      return this.get('selectedConfigGroup');
    } else {
      var stepConfig = this.get('stepConfigs').findProperty('serviceName', serviceName);
      if (stepConfig) {
        var groups = stepConfig.get('configGroups');
        if (this.get('selectedConfigGroup.isDefault')) {
          return groups.length ? groups.findProperty('isDefault', true) : null;
        } else {
          return groups.length ? groups.findProperty('name', this.get('selectedConfigGroup.dependentConfigGroups')[serviceName]) : null;
        }
      } else {
        return null;
      }
    }
  },

  clearRecommendationsInfo: function() {
    this.set('recommendationsConfigs', null);
  },

  /**
   * sends request to get values for dependent configs
   * @param {{type: string, name: string}[]} changedConfigs - list of changed configs to track recommendations
   * @param {Function} [onComplete]
   * @returns {$.ajax|null}
   */
  loadConfigRecommendations: function (changedConfigs, onComplete) {
    var self = this;
    var updateDependencies = Em.isArray(changedConfigs) && changedConfigs.length > 0;
    var stepConfigs = this.get('stepConfigs');
    if (updateDependencies || Em.isNone(this.get('recommendationsConfigs'))) {
      var configGroup = this.get('selectedConfigGroup');
      var recommendations = this.get('hostGroups');

      var dataToSend = {
        recommend: updateDependencies ? 'configuration-dependencies' : 'configurations',
        hosts: this.get('hostNames'),
        services: this.get('serviceNames')
      };
      if (updateDependencies) {
        dataToSend.changed_configurations = changedConfigs;
      }

      if (configGroup && !configGroup.get('isDefault') && configGroup.get('hosts.length') > 0) {
        recommendations.config_groups = [this.buildConfigGroupJSON(this.get('selectedService.configs'), configGroup)];
      } else {
        delete recommendations.config_groups;
      }

      if (stepConfigs.someProperty('serviceName', 'MISC')) {
        recommendations.blueprint.configurations = blueprintUtils.buildConfigsJSON(stepConfigs);
        dataToSend.recommendations = recommendations;
        return this.getRecommendationsRequest(dataToSend, onComplete);
      } else {
        App.config.getClusterEnvConfigs().done(function (clusterEnvConfigs) {
          stepConfigs = stepConfigs.concat(Em.Object.create({
            serviceName: 'MISC',
            configs: clusterEnvConfigs
          }));

          recommendations.blueprint.configurations = blueprintUtils.buildConfigsJSON(stepConfigs);
          dataToSend.recommendations = recommendations;
          return self.getRecommendationsRequest(dataToSend, onComplete);
        });
      }
    } else {
      if (onComplete) {
        onComplete()
      }
    }
    return $.Deferred().resolve().promise();
  },

  getRecommendationsRequest: function (dataToSend, callback) {
    var self = this;
    this.set('recommendationsInProgress', true);
    return App.ajax.send({
      name: 'config.recommendations',
      sender: self,
      data: {
        stackVersionUrl: App.get('stackVersionURL'),
        dataToSend: dataToSend
      },
      success: 'loadRecommendationsSuccess',
      error: 'loadRecommendationsError',
      callback: function () {
        self.set('recommendationsInProgress', false);
        self.set('recommendationTimeStamp', (new Date).getTime());
        if (callback) {
          callback()
        }
      }
    });
  },

  /**
   * Defines if there is any changes made by user.
   * Check all properties except recommended properties from popup
   *
   * @returns {boolean}
   */
  isConfigHasInitialState: function() {
    return this.get('selectedConfigGroup.isDefault') && !Em.isNone(this.get('recommendationsConfigs'))
      && !this.get('stepConfigs').filter(function(stepConfig) {
      return stepConfig.get('changedConfigProperties').filter(function(c) {
        return !this.get('changedProperties').map(function(changed) {
          return App.config.configId(changed.propertyName, changed.propertyFileName);
        }).contains(App.config.configId(c.get('name'), c.get('filename')));
      }, this).length;
    }, this).length;
  },

  /**
   * generates JSON with config group info to send it for recommendations
   * @param configs
   * @param configGroup
   * @returns {{configurations: Object[], hosts: string[]}}
   */
  buildConfigGroupJSON: function(configs, configGroup) {
    Em.assert('configGroup can\'t be null', configGroup);
    var hosts = configGroup.get('hosts');
    var configurations = {};
    var overrides = configs.forEach(function(cp) {
      var override = cp.get('overrides') && cp.get('overrides').findProperty('group.name', configGroup.get('name'));
      if (override) {
        var tag = App.config.getConfigTagFromFileName(cp.get('filename'));
        if (!configurations[tag]) {
          configurations[tag] = { properties: {} };
        }
        configurations[tag].properties[cp.get('name')] = override.get('value');
      }
    });
    return {
      configurations: [configurations],
      hosts: hosts
    }
  },

  /**
   * shows popup with results for recommended value
   * if case properties that was changes belongs to not default group
   * user should pick to what config group from dependent service dependent properties will be saved
   * @param data
   * @param opt
   * @param params
   * @method dependenciesSuccess
   */
  loadRecommendationsSuccess: function (data, opt, params) {
    this._saveRecommendedValues(data, params.dataToSend.changed_configurations);
    if (this.isConfigHasInitialState()) {
      this.undoRedoRecommended(this.get('recommendations'), false);
      this.clearAllRecommendations();
    }
    this.set("recommendationsConfigs", Em.get(data, "resources.0.recommendations.blueprint.configurations"));
  },

  loadRecommendationsError: Em.K,

  changedDependentGroup: function () {
    var dependentServices = this.get('selectedService.dependentServiceNames');
    var installedServices = App.Service.find().mapProperty('serviceName');
    var services = this.get('stepConfigs').filter(function (stepConfig) {
      return installedServices.contains(stepConfig.get('serviceName')) && dependentServices.contains(stepConfig.get('serviceName'));
    }, this);
    App.showSelectGroupsPopup(this.get('selectedService.serviceName'),
        this.get('selectedService.configGroups').findProperty('name', this.get('selectedConfigGroup.name')),
        services, this.get('recommendations'));
  },

  /**
   * saves values from response for dependent config properties to recommendations
   * @param data
   * @param [changedConfigs=null]
   * @method saveRecommendedValues
   * @private
   */
  _saveRecommendedValues: function(data, changedConfigs) {
    Em.assert('invalid data - `data.resources[0].recommendations.blueprint.configurations` not defined ', data && data.resources[0] && Em.get(data.resources[0], 'recommendations.blueprint.configurations'));
    var recommendations = data.resources[0].recommendations;
    if (recommendations['config-groups'] && this.get('selectedConfigGroup') && !this.get('selectedConfigGroup.isDefault')) {
      var configFroGroup = recommendations['config-groups'][0];
      this.get('stepConfigs').forEach(function(stepConfig) {
        var configGroup = this.getGroupForService(stepConfig.get('serviceName'));
        if (configGroup) {
          this.updateOverridesByRecommendations(configFroGroup.configurations, stepConfig.get('configs'), changedConfigs, configGroup);
          this.updateOverridesByRecommendations(configFroGroup.dependent_configurations, stepConfig.get('configs'), changedConfigs, configGroup);
          this.toggleProperty('forceUpdateBoundaries');
        }
      }, this);
    } else {
      var configObject = recommendations.blueprint.configurations;
      this.get('stepConfigs').forEach(function(stepConfig) {
        this.updateConfigsByRecommendations(configObject, stepConfig.get('configs'), changedConfigs);
      }, this);
      this.addByRecommendations(configObject, changedConfigs);
    }
    this.cleanUpRecommendations();
  },

  /**
   * method to show popup with dependent configs
   * @method showChangedDependentConfigs
   */
  showChangedDependentConfigs: function(event, callback, secondary) {
    var self = this;
    var recommendations = event ? this.get('changedProperties') : this.get('recommendations');
    if (recommendations.length > 0) {
      App.showDependentConfigsPopup(recommendations, function() {
        self.onSaveRecommendedPopup(recommendations);
        if (callback) callback();
      }, secondary);
    } else {
      if (callback) callback();
    }
  },

  /**
   * update configs when toggle checkbox on dependent configs popup
   */
  onSaveRecommendedPopup: function(recommendations) {
    var propertiesToUpdate = recommendations.filter(function(c) {
        return Em.get(c, 'saveRecommendedDefault') != Em.get(c, 'saveRecommended');
      }),
      propertiesToUndo = propertiesToUpdate.filterProperty('saveRecommended', false),
      propertiesToRedo = propertiesToUpdate.filterProperty('saveRecommended', true);

    this.undoRedoRecommended(propertiesToUndo, false);
    this.undoRedoRecommended(propertiesToRedo, true);
    this.set('recommendationTimeStamp', (new Date).getTime());
  },

  /**
   * run through config properties list (form dependent popup)
   * and set value to default (undo) or recommended (redo)
   * this happens when toggle checkbox in popup
   * @param {Object[]} propertiesToUpdate
   * @param {boolean} redo
   */
  undoRedoRecommended: function(propertiesToUpdate, redo) {
    propertiesToUpdate.forEach(function(p) {
      var initial = redo ? Em.get(p, 'initialValue') : Em.get(p, 'recommendedValue');
      var recommended = redo ? Em.get(p, 'recommendedValue') : Em.get(p, 'initialValue');
      var stepConfig = this.get('stepConfigs').findProperty('serviceName', Em.get(p, 'serviceName'));
      var config = stepConfig.get('configs').find(function(scp) {
        return scp.get('name') == Em.get(p, 'propertyName') && scp.get('filename') == App.config.getOriginalFileName(Em.get(p, 'propertyFileName'));
      });
      var selectedGroup = App.ServiceConfigGroup.find().filterProperty('serviceName', Em.get(p, 'serviceName')).findProperty('name', Em.get(p, 'configGroup'));
      if (selectedGroup.get('isDefault')) {
        if (Em.isNone(recommended)) {
          stepConfig.get('configs').removeObject(config);
        } else if (Em.isNone(initial)) {
          this._addConfigByRecommendation(stepConfig.get('configs'), Em.get(p, 'propertyName'), Em.get(p, 'propertyFileName'), recommended);
        } else {
          Em.set(config, 'value', recommended);
        }
      } else {
        if (Em.isNone(recommended)) {
          config.get('overrides').removeObject(config.getOverride(selectedGroup.get('name')));
        } else if (Em.isNone(initial)) {
          this._addConfigOverrideRecommendation(config, recommended, null, selectedGroup);
        } else {
          var override = config.getOverride(selectedGroup.get('name'));
          if (override) {
            override.set('value', recommended);
          }
        }
      }
    }, this);
  },

  /**
   * disable saving recommended value for current config
   * @param config
   * @param {boolean} saveRecommended
   * @method removeCurrentFromDependentList
   */
  removeCurrentFromDependentList: function (config, saveRecommended) {
    var recommendation = this.getRecommendation(config.get('name'), config.get('filename'), config.get('group.name'));
    if (recommendation) this.saveRecommendation(recommendation, saveRecommended);
  }
});
<commit_msg>AMBARI-17754 - Issue with modify configs for hive service (rzang)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 App = require('app');
var blueprintUtils = require('utils/blueprint');

App.EnhancedConfigsMixin = Em.Mixin.create(App.ConfigWithOverrideRecommendationParser, {

  /**
   * this value is used for observing
   * whether recommendations for dependent properties was received from server
   * @type {number}
   */
  recommendationTimeStamp: null,

  /**
   * this property is used to force update min/max values
   * for not default config groups
   * @type {boolean}
   */
  forceUpdateBoundaries: false,

  /**
   * object with loaded recommendations
   *
   * @type {Object}
   */
  recommendationsConfigs: null,

  /**
   * flag is true when Ambari changes some of the dependent properties
   * @type {boolean}
   */
  hasChangedDependencies: Em.computed.and('isControllerSupportsEnhancedConfigs', 'changedProperties.length'),

  /**
   * defines is block with changed dependent configs should be shown
   * rely on controller
   * @type {boolean}
   */
  isControllerSupportsEnhancedConfigs: Em.computed.existsIn('name', ['wizardStep7Controller','mainServiceInfoConfigsController']),

  dependenciesGroupMessage: Em.I18n.t('popup.dependent.configs.dependencies.for.groups'),
  /**
   * message fro alert box for dependent configs
   * @type {string}
   */
  dependenciesMessage: function() {
    var changedProperties = this.get('changedProperties').filterProperty('saveRecommended');
    var changedServices = changedProperties.mapProperty('serviceName').uniq();
    var cfgLenSuffix = changedProperties.length === 1 ? 'singular' : 'plural';
    var sLenSuffix = changedServices.length === 1 ? 'singular' : 'plural';
    return Em.I18n.t('popup.dependent.configs.dependencies.config.' + cfgLenSuffix).format(changedProperties.length)
      + Em.I18n.t('popup.dependent.configs.dependencies.service.' + sLenSuffix).format(changedServices.length);
  }.property('changedProperties.length'),

  /**
   * dependent properties that was changed by Ambari
   * @type {Object[]}
   */
  changedProperties: function() {
    return this.get('recommendations').filter(function(dp) {
      return (this.get('selectedConfigGroup.isDefault') && Em.get(dp, 'configGroup').contains('Default'))
        || [this.get('selectedConfigGroup.name'), this.get('selectedConfigGroup.dependentConfigGroups') && this.get('selectedConfigGroup.dependentConfigGroups')[Em.get(dp, 'serviceName')]].contains(Em.get(dp, 'configGroup'));
    }, this);
  }.property('recommendations.@each.saveRecommended', 'recommendations.@each.recommendedValue', 'selectedConfigGroup'),

  /**
   * defines if change dependent group message should be shown
   * @type {boolean}
   */
  showSelectGroupsPopup: Em.computed.and('!selectedConfigGroup.isDefault', 'selectedService.dependentServiceNames.length'),

  /**
   * set default values for dependentGroups
   * @method setDependentGroups
   */
  setDependentGroups: function () {
    if (this.get('selectedConfigGroup') && this.get('isControllerSupportsEnhancedConfigs') && !this.get('selectedConfigGroup.isDefault') && this.get('selectedService.dependentServiceNames.length')) {
      this.get('selectedService.dependentServiceNames').forEach(function (serviceName) {
        if (!this.get('selectedConfigGroup.dependentConfigGroups')[serviceName]) {
          var stepConfig = this.get('stepConfigs').findProperty('serviceName', serviceName);
          if (stepConfig) {
            stepConfig.get('configGroups').filterProperty('isDefault', false).forEach(function (configGroup) {
              this.get('selectedService.configGroups').filterProperty('isDefault', false).forEach(function (currentServiceGroup) {
                if (currentServiceGroup.get('dependentConfigGroups')[serviceName] != configGroup.get('name')) {
                  var dependentGroups = $.extend({},this.get('selectedConfigGroup.dependentConfigGroups'));
                  dependentGroups[serviceName] = configGroup.get('name');
                  this.set('selectedConfigGroup.dependentConfigGroups', dependentGroups);
                }
              }, this);
            }, this);
          }
        }
      }, this);
    }
  }.observes('selectedConfigGroup'),

  /******************************METHODS THAT WORKS WITH DEPENDENT CONFIGS *************************************/

  /**
   * get config group object for current service
   * @param serviceName
   * @returns {App.ConfigGroup|null}
   */
  getGroupForService: function(serviceName) {
    if (!this.get('stepConfigs') || this.get('stepConfigs.length') === 0) {
      return null;
    }
    if (this.get('selectedService.serviceName') === serviceName) {
      return this.get('selectedConfigGroup');
    } else {
      var stepConfig = this.get('stepConfigs').findProperty('serviceName', serviceName);
      if (stepConfig) {
        var groups = stepConfig.get('configGroups');
        if (this.get('selectedConfigGroup.isDefault')) {
          return groups.length ? groups.findProperty('isDefault', true) : null;
        } else {
          return groups.length ? groups.findProperty('name', this.get('selectedConfigGroup.dependentConfigGroups')[serviceName]) : null;
        }
      } else {
        return null;
      }
    }
  },

  clearRecommendationsInfo: function() {
    this.set('recommendationsConfigs', null);
  },

  /**
   * sends request to get values for dependent configs
   * @param {{type: string, name: string}[]} changedConfigs - list of changed configs to track recommendations
   * @param {Function} [onComplete]
   * @returns {$.ajax|null}
   */
  loadConfigRecommendations: function (changedConfigs, onComplete) {
    var self = this;
    var updateDependencies = Em.isArray(changedConfigs) && changedConfigs.length > 0;
    var stepConfigs = this.get('stepConfigs');
    if (updateDependencies || Em.isNone(this.get('recommendationsConfigs'))) {
      var configGroup = this.get('selectedConfigGroup');
      var recommendations = this.get('hostGroups');

      var dataToSend = {
        recommend: updateDependencies ? 'configuration-dependencies' : 'configurations',
        hosts: this.get('hostNames'),
        services: this.get('serviceNames')
      };
      if (updateDependencies) {
        dataToSend.changed_configurations = changedConfigs;
      }

      if (configGroup && !configGroup.get('isDefault') && configGroup.get('hosts.length') > 0) {
        recommendations.config_groups = [this.buildConfigGroupJSON(this.get('selectedService.configs'), configGroup)];
      } else {
        delete recommendations.config_groups;
      }

      if (stepConfigs.someProperty('serviceName', 'MISC')) {
        recommendations.blueprint.configurations = blueprintUtils.buildConfigsJSON(stepConfigs);
        dataToSend.recommendations = recommendations;
        return this.getRecommendationsRequest(dataToSend, onComplete);
      } else {
        App.config.getClusterEnvConfigs().done(function (clusterEnvConfigs) {
          stepConfigs = stepConfigs.concat(Em.Object.create({
            serviceName: 'MISC',
            configs: clusterEnvConfigs
          }));

          recommendations.blueprint.configurations = blueprintUtils.buildConfigsJSON(stepConfigs);
          dataToSend.recommendations = recommendations;
          return self.getRecommendationsRequest(dataToSend, onComplete);
        });
      }
    } else {
      if (onComplete) {
        onComplete()
      }
    }
    return $.Deferred().resolve().promise();
  },

  getRecommendationsRequest: function (dataToSend, callback) {
    var self = this;
    this.set('recommendationsInProgress', true);
    return App.ajax.send({
      name: 'config.recommendations',
      sender: self,
      data: {
        stackVersionUrl: App.get('stackVersionURL'),
        dataToSend: dataToSend
      },
      success: 'loadRecommendationsSuccess',
      error: 'loadRecommendationsError',
      callback: function () {
        self.set('recommendationsInProgress', false);
        self.set('recommendationTimeStamp', (new Date).getTime());
        if (callback) {
          callback()
        }
      }
    });
  },

  /**
   * Defines if there is any changes made by user.
   * Check all properties except recommended properties from popup
   *
   * @returns {boolean}
   */
  isConfigHasInitialState: function() {
    return this.get('selectedConfigGroup.isDefault') && !Em.isNone(this.get('recommendationsConfigs'))
      && !this.get('stepConfigs').filter(function(stepConfig) {
      return stepConfig.get('changedConfigProperties').filter(function(c) {
        return !this.get('changedProperties').map(function(changed) {
          return App.config.configId(changed.propertyName, changed.propertyFileName);
        }).contains(App.config.configId(c.get('name'), c.get('filename')));
      }, this).length;
    }, this).length;
  },

  /**
   * generates JSON with config group info to send it for recommendations
   * @param configs
   * @param configGroup
   * @returns {{configurations: Object[], hosts: string[]}}
   */
  buildConfigGroupJSON: function(configs, configGroup) {
    Em.assert('configGroup can\'t be null', configGroup);
    var hosts = configGroup.get('hosts');
    var configurations = {};
    var overrides = configs.forEach(function(cp) {
      var override = cp.get('overrides') && cp.get('overrides').findProperty('group.name', configGroup.get('name'));
      if (override) {
        var tag = App.config.getConfigTagFromFileName(cp.get('filename'));
        if (!configurations[tag]) {
          configurations[tag] = { properties: {} };
        }
        configurations[tag].properties[cp.get('name')] = override.get('value');
      }
    });
    return {
      configurations: [configurations],
      hosts: hosts
    }
  },

  /**
   * shows popup with results for recommended value
   * if case properties that was changes belongs to not default group
   * user should pick to what config group from dependent service dependent properties will be saved
   * @param data
   * @param opt
   * @param params
   * @method dependenciesSuccess
   */
  loadRecommendationsSuccess: function (data, opt, params) {
    this._saveRecommendedValues(data, params.dataToSend.changed_configurations);
    if (this.isConfigHasInitialState()) {
      this.undoRedoRecommended(this.get('recommendations'), false);
      this.clearAllRecommendations();
    }
    this.set("recommendationsConfigs", Em.get(data, "resources.0.recommendations.blueprint.configurations"));
  },

  loadRecommendationsError: Em.K,

  changedDependentGroup: function () {
    var dependentServices = this.get('selectedService.dependentServiceNames');
    var installedServices = App.Service.find().mapProperty('serviceName');
    var services = this.get('stepConfigs').filter(function (stepConfig) {
      return installedServices.contains(stepConfig.get('serviceName')) && dependentServices.contains(stepConfig.get('serviceName'));
    }, this);
    App.showSelectGroupsPopup(this.get('selectedService.serviceName'),
        this.get('selectedService.configGroups').findProperty('name', this.get('selectedConfigGroup.name')),
        services, this.get('recommendations'));
  },

  /**
   * saves values from response for dependent config properties to recommendations
   * @param data
   * @param [changedConfigs=null]
   * @method saveRecommendedValues
   * @private
   */
  _saveRecommendedValues: function(data, changedConfigs) {
    Em.assert('invalid data - `data.resources[0].recommendations.blueprint.configurations` not defined ', data && data.resources[0] && Em.get(data.resources[0], 'recommendations.blueprint.configurations'));
    var recommendations = data.resources[0].recommendations;
    if (recommendations['config-groups'] && this.get('selectedConfigGroup') && !this.get('selectedConfigGroup.isDefault')) {
      var configFroGroup = recommendations['config-groups'][0];
      this.get('stepConfigs').forEach(function(stepConfig) {
        var configGroup = this.getGroupForService(stepConfig.get('serviceName'));
        if (configGroup) {
          this.updateOverridesByRecommendations(configFroGroup.configurations, stepConfig.get('configs'), changedConfigs, configGroup);
          this.updateOverridesByRecommendations(configFroGroup.dependent_configurations, stepConfig.get('configs'), changedConfigs, configGroup);
          this.toggleProperty('forceUpdateBoundaries');
        }
      }, this);
    } else {
      var configObject = recommendations.blueprint.configurations;
      this.get('stepConfigs').forEach(function(stepConfig) {
        this.updateConfigsByRecommendations(configObject, stepConfig.get('configs'), changedConfigs);
      }, this);
      this.addByRecommendations(configObject, changedConfigs);
    }
    this.cleanUpRecommendations();
  },

  /**
   * method to show popup with dependent configs
   * @method showChangedDependentConfigs
   */
  showChangedDependentConfigs: function(event, callback, secondary) {
    var self = this;
    var recommendations = event ? this.get('changedProperties') : this.get('recommendations');
    if (recommendations.length > 0) {
      App.showDependentConfigsPopup(recommendations, function() {
        self.onSaveRecommendedPopup(recommendations);
        if (callback) callback();
      }, secondary);
    } else {
      if (callback) callback();
    }
  },

  /**
   * update configs when toggle checkbox on dependent configs popup
   */
  onSaveRecommendedPopup: function(recommendations) {
    var propertiesToUpdate = recommendations.filter(function(c) {
        return Em.get(c, 'saveRecommendedDefault') != Em.get(c, 'saveRecommended');
      }),
      propertiesToUndo = propertiesToUpdate.filterProperty('saveRecommended', false),
      propertiesToRedo = propertiesToUpdate.filterProperty('saveRecommended', true);

    this.undoRedoRecommended(propertiesToUndo, false);
    this.undoRedoRecommended(propertiesToRedo, true);
    this.set('recommendationTimeStamp', (new Date).getTime());
  },

  /**
   * run through config properties list (form dependent popup)
   * and set value to default (undo) or recommended (redo)
   * this happens when toggle checkbox in popup
   * @param {Object[]} propertiesToUpdate
   * @param {boolean} redo
   */
  undoRedoRecommended: function(propertiesToUpdate, redo) {
    propertiesToUpdate.forEach(function(p) {
      var initial = redo ? Em.get(p, 'initialValue') : Em.get(p, 'recommendedValue');
      var recommended = redo ? Em.get(p, 'recommendedValue') : Em.get(p, 'initialValue');
      var stepConfig = this.get('stepConfigs').findProperty('serviceName', Em.get(p, 'serviceName'));
      var config = stepConfig.get('configs').find(function(scp) {
        return scp.get('name') == Em.get(p, 'propertyName') && scp.get('filename') == App.config.getOriginalFileName(Em.get(p, 'propertyFileName'));
      });
      var selectedGroup = App.ServiceConfigGroup.find().filterProperty('serviceName', Em.get(p, 'serviceName')).findProperty('name', Em.get(p, 'configGroup'));
      if (selectedGroup.get('isDefault')) {
        if (Em.isNone(recommended)) {
          stepConfig.get('configs').removeObject(config);
        } else if (Em.isNone(initial)) {
          stepConfig.get('configs').pushObject(this._createNewProperty(Em.get(p, 'propertyName'),
            Em.get(p, 'propertyFileName'),Em.get(p, 'serviceName'), recommended, Em.get(p, 'parentConfigs')));
        } else {
          Em.set(config, 'value', recommended);
        }
      } else {
        if (Em.isNone(recommended)) {
          config.get('overrides').removeObject(config.getOverride(selectedGroup.get('name')));
        } else if (Em.isNone(initial)) {
          this._addConfigOverrideRecommendation(config, recommended, null, selectedGroup);
        } else {
          var override = config.getOverride(selectedGroup.get('name'));
          if (override) {
            override.set('value', recommended);
          }
        }
      }
    }, this);
  },

  /**
   * disable saving recommended value for current config
   * @param config
   * @param {boolean} saveRecommended
   * @method removeCurrentFromDependentList
   */
  removeCurrentFromDependentList: function (config, saveRecommended) {
    var recommendation = this.getRecommendation(config.get('name'), config.get('filename'), config.get('group.name'));
    if (recommendation) this.saveRecommendation(recommendation, saveRecommended);
  }
});
<|endoftext|>


<commit_before>//initialize things here if necessary
var move = "rock";

function respond(command) {
  if(command === "move")
    return move;
  else
    return "err";
}<commit_msg>Make js player a little more interesting<commit_after>//initialize things globally if necessary
var move = "rock";

function respond(command) {
  if(command === "move")
    return Math.random() < 0.5 ? "rock" : "paper";
  else
    return "err";
}<|endoftext|>