<commit_before>// Globals
var jsonFilename,
    jsonData,
    allPlotData,
    columnNames,
    selectionDispatch = {};

var svg;

function initialize(jsonFilename, csvFilename) {
    /*
        Initialize the D3PO figure given a JSON specification.

        Parameters
        ----------
        jsonFilename : string
            The relative path to a D3PO JSON spec file.
    */
    d3.json(jsonFilename, function(error, _tmp) {
        if (error) {
            console.warn(error);
            alert('Unable to find file "' + jsonFilename + '"');
        }
        jsonData = _tmp;

        // TODO: figure out type of file, use appropriate reader
        d3.csv(csvFilename, function(error, _tmp) {
            if (error) {
                console.warn(error);
                alert('Unable to read/parse file "' + csvFilename + '"');
            }
            allPlotData = _tmp;
            columnNames = d3.keys(allPlotData[0]);

            // draw the first state
            drawState(jsonData['states'][0]);

            var presets = d3.select("#controls .navigation").selectAll("li")
                            .data(jsonData['states']);

            presets.enter().append("li")
                   .on("click", function(e,i) {
                        drawState(jsonData['states'][i]);
                        $(".navigation li").removeClass("selected");
                        $(this).addClass("selected");
                   });

            presets.text(function(d, i){ return d['name']; });
            presets.exit().remove();

            $(".navigation li:first-of-type").addClass("selected");
        });
    });

    //
}

function domains(data, columns) {
    /*
        Get the domains (min, max) for each column in the plot data.
    */

    // Define an object to contain the domains (in data space) for each column
    var domainByDataColumn = {};

    columnNames.forEach(function(colName) {
        var domain = d3.extent(data, function(d) { return parseFloat(d[colName]); });

        // if parseFloat failed, probably string values in the column
        if (isNaN(domain[0]) || isNaN(domain[1])){
            var this_col = [];
            data.map(function(d) {
                this_col.push(d[colName]);
            });

            domainByDataColumn[colName] = this_col.unique();
        } else {
            var size = domain[1]-domain[0];
            domainByDataColumn[colName] = [domain[0] - size/25.,
                                           domain[1] + size/25.];
        }
    });

    return domainByDataColumn;
}

function isSelectedBox(selection, d) {
    // test for a selection based on a box
    var xr = selection['xRange'],
        yr = selection['yRange'];
    var e = [
        [xr[0], yr[0]],
        [xr[1], yr[1]]
    ];

    var xCol = selection['xAxis']['label'],
        yCol = selection['yAxis']['label'];

    if (e[0][0] > d[xCol] || d[xCol] > e[1][0] || e[0][1] > d[yCol] || d[yCol] > e[1][1]) {
        return false;
    } else {
        return true;
    }
}

selectionDispatch['box'] = isSelectedBox;

function isSelectedAttribute(selection, d) {
    // test for selection based on the value of a binary-valued column in the data
    var att = selection['attribute'];
    return d[att] > 0;
}
selectionDispatch['attribute'] = isSelectedAttribute;

function isSelected(selection, d) {
    if ($.isEmptyObject(selection)) {
        return true;
    } // empty selection defaults to select all

    // determine what kind of selection to perform
    var selectionType = selection['type'] || 'box';
    return selectionDispatch[selectionType](selection, d);
}

function drawState(state) {
/*
    TODO
*/
    var nRows = state['grid']['nRows'],
        nCols = state['grid']['nColumns'],
        figSpec = state['figure'] || dFigureSpec;

    var figPadding = figSpec["figurePadding"],
        plotSpacing = figSpec["plotSpacing"],
        plotSize = figSpec["plotSize"];

    // Compute the height / width of the svg element based on the plot size,
    // plot spacing, and figure padding.
    var svg_height = nRows*(plotSize['height'] + plotSpacing['vertical']) +
                     figPadding['top'] + figPadding['bottom'],
        svg_width = nCols*(plotSize['width'] + plotSpacing['horizontal']) + plotSpacing['horizontal']
                     figPadding['left'] + figPadding['right'];

    // Define top level svg tag
    svg = d3.select("#svg svg");
    svg.transition()
       .duration(500)
       .ease('quad-out')
       .attr("width", svg_width)
       .attr("height", svg_height)
       .each("end", function () {
            $("body").toggleClass("hack");
            setTimeout(function (){ $("body").toggleClass("hack"); });
        });

    svg.append("g")
       .attr("transform", "translate(" + figPadding["left"] + "," + figPadding["top"] + ")");

    // Scalers for x / y axes from data space to pixel space
    // TODO: build in log support here - if statement? need to keep track of what
    //       axes are log, which are linear, etc.
    var xScaler = d3.scale.linear()
                    .range([plotSpacing['horizontal']/2,
                            plotSize['width'] + plotSpacing['horizontal']/2]),
        yScaler = d3.scale.linear()
                    .range([plotSize['height'] + plotSpacing['vertical']/2,
                            plotSpacing['vertical']/2]),
        cScaler = d3.scale.linear();

    // Functions to compute the amount to translate the individual plots by
    xPlotTranslator = function (plot) {
        index = plot["gridPosition"][1];
        return index*(plotSize['width'] + plotSpacing['horizontal']) + plotSpacing['horizontal']/2.;
    }
    yPlotTranslator = function (plot) {
        index = plot["gridPosition"][0];
        return (nRows - index - 1)*(plotSize['height'] + plotSpacing['vertical']);
    }

    // Get the domains in data units for each column in the data file
    var columnDomains = domains(allPlotData, columnNames);

    // if this plot has a color-by axis, set the domain and range of the color scaler
    if (typeof state['colorAxis'] != 'undefined') {
        cScaler.domain(columnDomains[state['colorAxis']]);
        cScaler.range(state['colorMap'] || dColorMap);
    }

    // set up the selection object if some points are highlighted
    var selection = {};
    for (var ii=0; ii < state['plots'].length; ii++) {
        var p = state['plots'][ii];
        if (typeof p['selection'] != 'undefined') {
            selection['xAxis'] = p['xAxis']['label'];
            selection['yAxis'] = p['yAxis']['label'];
            for (var item in p['selection'])
                selection[item] = p['selection'][item];
        }
    }

    // Set up a cell group for each plot window, to then draw points and rectangle over
    var cells = d3.select("svg").selectAll("g.cell").data(state["plots"]);
    cells.enter().append("g")
         .attr("class", "cell");
    cells.exit().remove();
    cells.transition().duration(500).ease('quad-out')
         .attr("transform", function(p) { return "translate(" + xPlotTranslator(p) + ","
                                                              + yPlotTranslator(p) + ")"; })
         .each(plot);

    // Define the brush object
    var brush = d3.svg.brush()
                  .x(xScaler).y(yScaler)
                  .on("brushstart", brushstart)
                  .on("brush", brushmove)
                  .on("brushend", brushend);

    // have the cells call the brush
    cells.call(brush);

    // Add axes to the plots
    // TODO: figure out how to give user better control over ticks?
    var xAxisD3 = d3.svg.axis()
                .scale(xScaler)
                .orient("bottom")
                .ticks(5)
                .tickSize(dTickSize);

    var yAxisD3 = d3.svg.axis()
                .scale(yScaler)
                .orient("left")
                .ticks(5)
                .tickSize(dTickSize);

    // Draw / update axes
    var xAxis = d3.select("svg").selectAll(".x.axis")
                  .data(state["plots"]);
    xAxis.enter().append("g")
         .attr("class", "x axis");
    xAxis.exit().remove();
    xAxis.transition().duration(500).ease('quad-out')
         .attr("transform", function(p, i) {
            return "translate(" + xPlotTranslator(p) + ","
                                + (yPlotTranslator(p) + plotSize['height'] + 15) + ")";
        }).each(function(p) {
            xScaler.domain(columnDomains[p['xAxis']['label']]);
            d3.select(this).call(xAxisD3);
        });

    var yAxis = d3.select("svg").selectAll(".y.axis")
                  .data(state["plots"]);
    yAxis.enter().append("g")
         .attr("class", "y axis");
    yAxis.exit().remove();
    yAxis.transition().duration(500).ease('quad-out')
         .attr("transform", function(p, i) {
            return "translate(" + (xPlotTranslator(p) + plotSpacing['horizontal']/2 + 10) + ","
                                + yPlotTranslator(p) + ")";
        }).each(function(p) {
            yScaler.domain(columnDomains[p['yAxis']['label']]);
            d3.select(this).call(yAxisD3);
        });

    // Add axis labels
    var xLabel = svg.selectAll(".x-label")
                    .data(state["plots"]);
    xLabel.enter().append("text")
          .attr("class", "axis-label x-label");
    xLabel.exit().remove();
    xLabel.text(function(p, i) { return p['xAxis']['label']; });
    xLabel.attr("x", function(p) { return xPlotTranslator(p) + plotSpacing['horizontal']/2 + plotSize['width']/2. - $(this).width()/2.; })
          .attr("y", function(p) { return yPlotTranslator(p) + plotSpacing['vertical']/2 + plotSize['height'] + 50; });

    var yLabel = svg.selectAll(".y-label")
                    .data(state["plots"]);
    yLabel.enter().append("text")
          .attr("class", "axis-label y-label");
    yLabel.exit().remove();
    yLabel.text(function(p,i) { return p['yAxis']['label']; });
    yLabel.attr("x", function(p) { return -((plotSpacing['vertical']/2-yPlotTranslator(p)) + plotSize['height']/2. + $(this).width()/2.); }) // deliberately backwards cause rotated
          .attr("y", function(p) { return xPlotTranslator(p); });

    var brushCell;
    // Clear the previously-active brush, if any.
    function brushstart(p) {
        if (brushCell !== this) {
            d3.select(brushCell).call(brush.clear());
            xScaler.domain(columnDomains[p['xAxis']['label']]);
            yScaler.domain(columnDomains[p['yAxis']['label']]);
            brushCell = this;
        }
    }

    // Highlight the selected circles.
    function brushmove(p) {
        var e = brush.extent(),
            xCol = p['xAxis']['label'],
            yCol = p['yAxis']['label'];

        svg.selectAll("circle")
           .style("fill", function (d) { return cScaler(d[state['colorAxis']]) || dMarkerFill; })
           .attr("opacity", function (d) { return dMarkerOpacity; })
           .classed("hidden", function(d) {
              return e[0][0] > d[xCol] || d[xCol] > e[1][0]
                      || e[0][1] > d[yCol] || d[yCol] > e[1][1];
        });
    }

    // If the brush is empty, select all circles.
    function brushend() {
        if (brush.empty()) svg.selectAll("circle").classed("hidden", false);
    }

    // TODO: wtf
    function plot(p) {
        var cell = d3.select(this);

        xScaler.domain(columnDomains[p['xAxis']['label']]);
        yScaler.domain(columnDomains[p['yAxis']['label']]);

        var marker = p['marker'];

        if (typeof marker != 'undefined') {
            opacity = marker['opacity'] || dMarkerOpacity;
            size = marker['size'] || dMarkerSize;
            fill = marker['fill'] || dMarkerFill;
        } else {
            opacity = dMarkerOpacity;
            size = dMarkerSize;
            fill = dMarkerFill;
        }

        var rect = cell.selectAll("rect.frame").data([1]);
        rect.enter().append("rect");
        rect.exit().remove();
        rect.transition().duration(500).ease("quad-out")
            .attr("class", "frame")
            .attr("x", plotSpacing['horizontal'] / 2)
            .attr("y", plotSpacing['vertical'] / 2)
            .attr("width", plotSize['width'])
            .attr("height", plotSize['height']);

        var circ = cell.selectAll("circle").data(allPlotData)
        circ.enter().append("circle");
        circ.exit().remove();
        circ.transition().duration(500).ease("quad-out")
            .attr("cx", function(d) { return xScaler(d[p['xAxis']['label']]); })
            .attr("cy", function(d) { return yScaler(d[p['yAxis']['label']]); })
            .attr("r", size)
            .style("fill", function (d) {
                if (isSelected(selection, d)) {
                    return cScaler(d[state['colorAxis']]) || fill;
                } else {
                    return "#cccccc";
                }
            })
            .attr("opacity", function (d) {
                if (isSelected(selection, d)) {
                    return opacity;
                } else {
                    return 0.5;
                }
            });
    }

    // Finally, update caption
    var caption = d3.select("#caption")
                    .style('opacity', 0)
                    .text(state.caption || "")
                    .transition()
                    .duration(500)
                    .style('opacity', 1);
}<commit_msg>implemented axis ranges. closes #21<commit_after>// Globals
var jsonFilename,
    jsonData,
    allPlotData,
    columnNames,
    selectionDispatch = {};

var svg;

function initialize(jsonFilename, csvFilename) {
    /*
        Initialize the D3PO figure given a JSON specification.

        Parameters
        ----------
        jsonFilename : string
            The relative path to a D3PO JSON spec file.
    */
    d3.json(jsonFilename, function(error, _tmp) {
        if (error) {
            console.warn(error);
            alert('Unable to find file "' + jsonFilename + '"');
        }
        jsonData = _tmp;

        // TODO: figure out type of file, use appropriate reader
        d3.csv(csvFilename, function(error, _tmp) {
            if (error) {
                console.warn(error);
                alert('Unable to read/parse file "' + csvFilename + '"');
            }
            allPlotData = _tmp;
            columnNames = d3.keys(allPlotData[0]);

            // draw the first state
            drawState(jsonData['states'][0]);

            var presets = d3.select("#controls .navigation").selectAll("li")
                            .data(jsonData['states']);

            presets.enter().append("li")
                   .on("click", function(e,i) {
                        drawState(jsonData['states'][i]);
                        $(".navigation li").removeClass("selected");
                        $(this).addClass("selected");
                   });

            presets.text(function(d, i){ return d['name']; });
            presets.exit().remove();

            $(".navigation li:first-of-type").addClass("selected");
        });
    });

    //
}

function domains(data, columns) {
    /*
        Get the domains (min, max) for each column in the plot data.
    */

    // Define an object to contain the domains (in data space) for each column
    var domainByDataColumn = {};

    columnNames.forEach(function(colName) {
        var domain = d3.extent(data, function(d) { return parseFloat(d[colName]); });

        // if parseFloat failed, probably string values in the column
        if (isNaN(domain[0]) || isNaN(domain[1])){
            var this_col = [];
            data.map(function(d) {
                this_col.push(d[colName]);
            });

            domainByDataColumn[colName] = this_col.unique();
        } else {
            var size = domain[1]-domain[0];
            domainByDataColumn[colName] = [domain[0] - size/25.,
                                           domain[1] + size/25.];
        }
    });

    return domainByDataColumn;
}

function isSelectedBox(selection, d) {
    // test for a selection based on a box
    var xr = selection['xRange'],
        yr = selection['yRange'];
    var e = [
        [xr[0], yr[0]],
        [xr[1], yr[1]]
    ];

    var xCol = selection['xAxis']['label'],
        yCol = selection['yAxis']['label'];

    if (e[0][0] > d[xCol] || d[xCol] > e[1][0] || e[0][1] > d[yCol] || d[yCol] > e[1][1]) {
        return false;
    } else {
        return true;
    }
}

selectionDispatch['box'] = isSelectedBox;

function isSelectedAttribute(selection, d) {
    // test for selection based on the value of a binary-valued column in the data
    var att = selection['attribute'];
    return d[att] > 0;
}
selectionDispatch['attribute'] = isSelectedAttribute;

function isSelected(selection, d) {
    if ($.isEmptyObject(selection)) {
        return true;
    } // empty selection defaults to select all

    // determine what kind of selection to perform
    var selectionType = selection['type'] || 'box';
    return selectionDispatch[selectionType](selection, d);
}

function drawState(state) {
/*
    TODO
*/
    var nRows = state['grid']['nRows'],
        nCols = state['grid']['nColumns'],
        figSpec = state['figure'] || dFigureSpec;

    var figPadding = figSpec["figurePadding"],
        plotSpacing = figSpec["plotSpacing"],
        plotSize = figSpec["plotSize"];

    // Compute the height / width of the svg element based on the plot size,
    // plot spacing, and figure padding.
    var svg_height = nRows*(plotSize['height'] + plotSpacing['vertical']) +
                     figPadding['top'] + figPadding['bottom'],
        svg_width = nCols*(plotSize['width'] + plotSpacing['horizontal']) + plotSpacing['horizontal']
                     figPadding['left'] + figPadding['right'];

    // Define top level svg tag
    svg = d3.select("#svg svg");
    svg.transition()
       .duration(500)
       .ease('quad-out')
       .attr("width", svg_width)
       .attr("height", svg_height)
       .each("end", function () {
            $("body").toggleClass("hack");
            setTimeout(function (){ $("body").toggleClass("hack"); });
        });

    svg.append("g")
       .attr("transform", "translate(" + figPadding["left"] + "," + figPadding["top"] + ")");

    // Scalers for x / y axes from data space to pixel space
    // TODO: build in log support here - if statement? need to keep track of what
    //       axes are log, which are linear, etc.
    var xScaler = d3.scale.linear()
                    .range([plotSpacing['horizontal']/2,
                            plotSize['width'] + plotSpacing['horizontal']/2]),
        yScaler = d3.scale.linear()
                    .range([plotSize['height'] + plotSpacing['vertical']/2,
                            plotSpacing['vertical']/2]),
        cScaler = d3.scale.linear();

    // Functions to compute the amount to translate the individual plots by
    xPlotTranslator = function (plot) {
        index = plot["gridPosition"][1];
        return index*(plotSize['width'] + plotSpacing['horizontal']) + plotSpacing['horizontal']/2.;
    }
    yPlotTranslator = function (plot) {
        index = plot["gridPosition"][0];
        return (nRows - index - 1)*(plotSize['height'] + plotSpacing['vertical']);
    }

    // Get the domains in data units for each column in the data file
    var columnDomains = domains(allPlotData, columnNames);

    // if this plot has a color-by axis, set the domain and range of the color scaler
    if (typeof state['colorAxis'] != 'undefined') {
        cScaler.domain(columnDomains[state['colorAxis']]);
        cScaler.range(state['colorMap'] || dColorMap);
    }

    // set up the selection object if some points are highlighted
    var selection = {};
    for (var ii=0; ii < state['plots'].length; ii++) {
        var p = state['plots'][ii];
        if (typeof p['selection'] != 'undefined') {
            selection['xAxis'] = p['xAxis']['label'];
            selection['yAxis'] = p['yAxis']['label'];
            for (var item in p['selection'])
                selection[item] = p['selection'][item];
        }
    }

    // Set up a cell group for each plot window, to then draw points and rectangle over
    var cells = d3.select("svg").selectAll("g.cell").data(state["plots"]);
    cells.enter().append("g")
         .attr("class", "cell");
    cells.exit().remove();
    cells.transition().duration(500).ease('quad-out')
         .attr("transform", function(p) { return "translate(" + xPlotTranslator(p) + ","
                                                              + yPlotTranslator(p) + ")"; })
         .each(plot);

    // Define the brush object
    var brush = d3.svg.brush()
                  .x(xScaler).y(yScaler)
                  .on("brushstart", brushstart)
                  .on("brush", brushmove)
                  .on("brushend", brushend);

    // have the cells call the brush
    cells.call(brush);

    // Add axes to the plots
    // TODO: figure out how to give user better control over ticks?
    var xAxisD3 = d3.svg.axis()
                .scale(xScaler)
                .orient("bottom")
                .ticks(5)
                .tickSize(dTickSize);

    var yAxisD3 = d3.svg.axis()
                .scale(yScaler)
                .orient("left")
                .ticks(5)
                .tickSize(dTickSize);

    // Draw / update axes
    var xAxis = d3.select("svg").selectAll(".x.axis")
                  .data(state["plots"]);
    xAxis.enter().append("g")
         .attr("class", "x axis");
    xAxis.exit().remove();
    xAxis.transition().duration(500).ease('quad-out')
         .attr("transform", function(p, i) {
            return "translate(" + xPlotTranslator(p) + ","
                                + (yPlotTranslator(p) + plotSize['height'] + 15) + ")";
        }).each(function(p) {
            xScaler.domain(p['xAxis']['range'] || columnDomains[p['xAxis']['label']]);
            d3.select(this).call(xAxisD3);
        });

    var yAxis = d3.select("svg").selectAll(".y.axis")
                  .data(state["plots"]);
    yAxis.enter().append("g")
         .attr("class", "y axis");
    yAxis.exit().remove();
    yAxis.transition().duration(500).ease('quad-out')
         .attr("transform", function(p, i) {
            return "translate(" + (xPlotTranslator(p) + plotSpacing['horizontal']/2 + 10) + ","
                                + yPlotTranslator(p) + ")";
        }).each(function(p) {
            yScaler.domain(p['yAxis']['range'] || columnDomains[p['yAxis']['label']]);
            d3.select(this).call(yAxisD3);
        });

    // Add axis labels
    var xLabel = svg.selectAll(".x-label")
                    .data(state["plots"]);
    xLabel.enter().append("text")
          .attr("class", "axis-label x-label");
    xLabel.exit().remove();
    xLabel.text(function(p, i) { return p['xAxis']['label']; });
    xLabel.attr("x", function(p) { return xPlotTranslator(p) + plotSpacing['horizontal']/2 + plotSize['width']/2. - $(this).width()/2.; })
          .attr("y", function(p) { return yPlotTranslator(p) + plotSpacing['vertical']/2 + plotSize['height'] + 50; });

    var yLabel = svg.selectAll(".y-label")
                    .data(state["plots"]);
    yLabel.enter().append("text")
          .attr("class", "axis-label y-label");
    yLabel.exit().remove();
    yLabel.text(function(p,i) { return p['yAxis']['label']; });
    yLabel.attr("x", function(p) { return -((plotSpacing['vertical']/2-yPlotTranslator(p)) + plotSize['height']/2. + $(this).width()/2.); }) // deliberately backwards cause rotated
          .attr("y", function(p) { return xPlotTranslator(p); });

    var brushCell;
    // Clear the previously-active brush, if any.
    function brushstart(p) {
        if (brushCell !== this) {
            d3.select(brushCell).call(brush.clear());
            xScaler.domain(p['xAxis']['range'] || columnDomains[p['xAxis']['label']]);
            yScaler.domain(p['yAxis']['range'] || columnDomains[p['yAxis']['label']]);
            brushCell = this;
        }
    }

    // Highlight the selected circles.
    function brushmove(p) {
        var e = brush.extent(),
            xCol = p['xAxis']['label'],
            yCol = p['yAxis']['label'];

        svg.selectAll("circle")
           .style("fill", function (d) { return cScaler(d[state['colorAxis']]) || dMarkerFill; })
           .attr("opacity", function (d) { return dMarkerOpacity; })
           .classed("hidden", function(d) {
              return e[0][0] > d[xCol] || d[xCol] > e[1][0]
                      || e[0][1] > d[yCol] || d[yCol] > e[1][1];
        });
    }

    // If the brush is empty, select all circles.
    function brushend() {
        if (brush.empty()) svg.selectAll("circle").classed("hidden", false);
    }

    // TODO: wtf
    function plot(p) {
        var cell = d3.select(this);

        xScaler.domain(p['xAxis']['range'] || columnDomains[p['xAxis']['label']]);
        yScaler.domain(p['yAxis']['range'] || columnDomains[p['yAxis']['label']]);

        var marker = p['marker'];

        if (typeof marker != 'undefined') {
            opacity = marker['opacity'] || dMarkerOpacity;
            size = marker['size'] || dMarkerSize;
            fill = marker['fill'] || dMarkerFill;
        } else {
            opacity = dMarkerOpacity;
            size = dMarkerSize;
            fill = dMarkerFill;
        }

        var rect = cell.selectAll("rect.frame").data([1]);
        rect.enter().append("rect");
        rect.exit().remove();
        rect.transition().duration(500).ease("quad-out")
            .attr("class", "frame")
            .attr("x", plotSpacing['horizontal'] / 2)
            .attr("y", plotSpacing['vertical'] / 2)
            .attr("width", plotSize['width'])
            .attr("height", plotSize['height']);

        svg.append("defs").append("clipPath")
                          .attr("id", "clip")
                          .append("rect")
                          .attr("x", plotSpacing['horizontal'] / 2)
                          .attr("y", plotSpacing['vertical'] / 2)
                          .attr("width", plotSize['width'])
                          .attr("height", plotSize['height']);

        var circ = cell.selectAll("circle").data(allPlotData)
        circ.enter().append("circle");
        circ.exit().remove();
        circ.transition().duration(500).ease("quad-out")
            .attr("cx", function(d) { return xScaler(d[p['xAxis']['label']]); })
            .attr("cy", function(d) { return yScaler(d[p['yAxis']['label']]); })
            .attr("r", size)
            .style("fill", function (d) {
                if (isSelected(selection, d)) {
                    return cScaler(d[state['colorAxis']]) || fill;
                } else {
                    return "#cccccc";
                }
            })
            .attr("opacity", function (d) {
                if (isSelected(selection, d)) {
                    return opacity;
                } else {
                    return 0.5;
                }
            })
            .attr("clip-path", "url(#clip)");
    }

    // Finally, update caption
    var caption = d3.select("#caption")
                    .style('opacity', 0)
                    .text(state.caption || "")
                    .transition()
                    .duration(500)
                    .style('opacity', 1);
}<|endoftext|>


<commit_before>var exec = require("child_process").exec;
var _ = require("underscore");
var LsParser =  require("./lib/lsParser").LsParser;

var NodeFinder = function(options){
	var _options = options || {};
	var settings = this.settings = {
			preloaded: false
	};
	var startfrom = settings.startfrom = _options.startfrom || null;
	var source = settings.source = _options.source || null;

	this.lsParser = new LsParser(startfrom,source) || {};
	this.db = null;
};

 NodeFinder.prototype.start = function(callback){
 	var self = this;
 	var settings = this.settings;
	if(!settings.preloaded){
		exec("ls -R -A -l " + settings.startfrom + " | grep -v ^d",
			function(error,stdout,stderr){
				//console.log(stdout);
				self.db = self.lsParser.toList(stdout) || null;

				callback(self,self.db);
				//settings.preloaded = true;
		});
	}else{
		callback(self,this.db);
	}
};

NodeFinder.prototype._typeFilter = function(typeName){
	var db = this.db;
	var query = _.filter(db,function(record){
		return record.type == typeName;
	});
	return query;
}
NodeFinder.prototype._pathFilter = function(path){
	if(!_.isArray(path)) return;

	var db = this.db;
		qu = _.filter(db,function(record){
			return record.path.length == path.length;
		});
	var query = _.filter(qu,function(record){
		return _.isEmpty(_.difference(record.path,path));
	});
	return query;
};

NodeFinder.prototype._find = function(selector){
	if(_.isNull(selector) || _.isUndefined(selector) ||  _.isEmpty(selector)) return this;

	if(_.isString(selector)){
		switch(_.first(selector)){
			//Find By Name
			case "#":
				console.log("findByName is not implemented.");
				break;
			//Find By Type
			case ".":
				return this._typeFilter(_.rest(selector).join(''));
				break;
			//Find By Path
			default:
				return this;
		}
	}else{
		var options = selector || {};
		var name = options.name || null;
		var type = options.type || null;
		var path = options.path || null;
		if(path != null){
			//console.log("_pathFilter");
			return this._pathFilter(path);
		}
		//if(type != null)
		//	return this._typeFilter(type);
	}
};

NodeFinder.prototype.find = function(selector,callback){
	var settings = this.settings;

	if(!settings.preloaded){
		this.start(function(finder){
			var result = finder._find(selector);
			callback(result);
		});
	}else{
		var result = this._find(selector);
		return result;
	}
};

exports.NodeFinder = NodeFinder;<commit_msg>change toList -> toDictionary<commit_after>var exec = require("child_process").exec;
var _ = require("underscore");
var LsParser =  require("./lib/lsParser").LsParser;

var NodeFinder = function(options){
	var _options = options || {};
	var settings = this.settings = {
			preloaded: false
	};
	var startfrom = settings.startfrom = _options.startfrom || null;
	var source = settings.source = _options.source || null;

	this.lsParser = new LsParser(startfrom,source) || {};
	this.db = null;
};

 NodeFinder.prototype.start = function(callback){
 	var self = this;
 	var settings = this.settings;
	if(!settings.preloaded){
		exec("ls -R -A -l " + settings.startfrom + " | grep -v ^d",
			function(error,stdout,stderr){
				//console.log(stdout);
				self.db = self.lsParser.toDictionary(stdout) || null;

				callback(self,self.db);
				//settings.preloaded = true;
		});
	}else{
		callback(self,this.db);
	}
};

NodeFinder.prototype._typeFilter = function(typeName){
	var db = this.db;
	var query = _.filter(db,function(record){
		return record.type == typeName;
	});
	return query;
}
NodeFinder.prototype._pathFilter = function(path){
	if(!_.isArray(path)) return;

	var db = this.db;
		qu = _.filter(db,function(record){
			return record.path.length == path.length;
		});
	var query = _.filter(qu,function(record){
		return _.isEmpty(_.difference(record.path,path));
	});
	return query;
};

NodeFinder.prototype._find = function(selector){
	if(_.isNull(selector) || _.isUndefined(selector) ||  _.isEmpty(selector)) return this;

	if(_.isString(selector)){
		switch(_.first(selector)){
			//Find By Name
			case "#":
				console.log("findByName is not implemented.");
				break;
			//Find By Type
			case ".":
				return this._typeFilter(_.rest(selector).join(''));
				break;
			//Find By Path
			default:
				return this;
		}
	}else{
		var options = selector || {};
		var name = options.name || null;
		var type = options.type || null;
		var path = options.path || null;
		if(path != null){
			//console.log("_pathFilter");
			return this._pathFilter(path);
		}
		//if(type != null)
		//	return this._typeFilter(type);
	}
};

NodeFinder.prototype.find = function(selector,callback){
	var settings = this.settings;

	if(!settings.preloaded){
		this.start(function(finder){
			var result = finder._find(selector);
			callback(result);
		});
	}else{
		var result = this._find(selector);
		return result;
	}
};

exports.NodeFinder = NodeFinder;<|endoftext|>


<commit_before>const Promise = require('bluebird');
const util = require('../util');
const cache = require('../cache');
const products = require('./products');

const categories = module.exports;

// Init routes
categories.init = (env, router, schema) => {
  router.get('/categories/:id', cache(env), categories.getById.bind(this, schema));
  router.get('/categories/:id/children', cache(env), categories.getChildren.bind(this, schema));
  router.get('/categories/:id/products', cache(env), categories.getAllProducts.bind(this, schema));
};

// Get category by ID
categories.getById = (schema, req) => {
  return schema.get('/categories/{id}', {
    id: req.params.id,
    where: categories.defaultQuery(req),
  });
};

// Get category children
categories.getChildren = (schema, req) => {
  return schema.get('/categories/{id}/children', {
    id: req.params.id,
    where: categories.defaultQuery(req),
    include: categories.filterIncludeQuery(req),
  });
};

// Get all nested category products
categories.getAllProducts = (schema, req) => {
  return schema.get('/categories/{id}', {
    id: req.params.id,
    fields: 'id'
  }).then(category => {
    if (!category) {
      return null;
    }
    return categories.getAllChildIdsRecursive(schema, req, [ category.id ]).then(allCategoryIds => {
      if (!allCategoryIds.length) {
        return null;
      }
      return schema.get('/products', {
        $or: allCategoryIds.map(categoryId => {
          return {
            'category_index.id': categoryId
          };
        }),
        where: products.defaultQuery(req),
      });
    });
  });
};

// Get child category IDs recursively
categories.getAllChildIdsRecursive = (schema, req, parentIds, ids) => {
  ids = ids || [];
  return schema.get('/categories', {
    id: { $in: parentIds },
    fields: 'id',
    where: categories.defaultQuery(req),
    include: {
      children: {
        url: '/categories',
        params: {
          parent_id: 'id'
        },
        data: {
          fields: 'id',
          limit: null
        }
      }
    }
  }).then(result => {
    if (!result || !result.results.length) {
      return ids;
    }
    return Promise.all(
      result.results.map(category => {
        ids.push(category.id);
        if (category.children.results.length) {
          const nextIds = category.children.results.map(child => child.id);
          return new Promise(resolve => {
            categories.getAllChildIdsRecursive(schema, req, nextIds, ids).then(resolve);
          });
        }
      })
    ).then(() => {
      return ids;
    });
  });
};

// Get a category

// Default query
categories.defaultQuery = (req) => {
  return {
    active: true,
    navigation: req.query.navigation || undefined,
  }
};

// Filter include query for categories
categories.filterIncludeQuery = (req) => {
  let include = {};
  if (req.query.depth) {
    include = categories.filterIncludeQueryDepth(include, req);
  }
  return include;
};

// Filter depth query to include nested child categories
categories.filterIncludeQueryDepth = (include, req) => {
  // Depth query specifies how deep the children should fetch
  let depth = req.query.depth;
  if (depth > 1) {
    if (depth > 5) {
      throw util.error(400, 'Too much depth');
    }
    let deepInclude = include;
    while (--depth) {
      deepInclude.children = {};
      deepInclude.children.url = '/categories',
      deepInclude.children.params = { parent_id: 'id' };
      deepInclude.children.data = { include: {} };
      // Recursion here
      deepInclude = deepInclude.children.data.include;
    }
  }
  return include;
};
<commit_msg>Category sorting<commit_after>const Promise = require('bluebird');
const util = require('../util');
const cache = require('../cache');
const products = require('./products');

const categories = module.exports;

// Init routes
categories.init = (env, router, schema) => {
  router.get('/categories/:id', cache(env), categories.getById.bind(this, schema));
  router.get('/categories/:id/children', cache(env), categories.getChildren.bind(this, schema));
  router.get('/categories/:id/products', cache(env), categories.getAllProducts.bind(this, schema));
};

// Get category by ID
categories.getById = (schema, req) => {
  return schema.get('/categories/{id}', {
    id: req.params.id,
    where: categories.defaultQuery(req),
    sort: categories.defaultSort(req),
  });
};

// Get category children
categories.getChildren = (schema, req) => {
  return schema.get('/categories/{id}/children', {
    id: req.params.id,
    where: categories.defaultQuery(req),
    sort: categories.defaultSort(req),
    include: categories.filterIncludeQuery(req),
  });
};

// Get all nested category products
categories.getAllProducts = (schema, req) => {
  return schema.get('/categories/{id}', {
    id: req.params.id,
    fields: 'id'
  }).then(category => {
    if (!category) {
      return null;
    }
    return categories.getAllChildIdsRecursive(schema, req, [ category.id ]).then(allCategoryIds => {
      if (!allCategoryIds.length) {
        return null;
      }
      return schema.get('/products', {
        $or: allCategoryIds.map(categoryId => {
          return {
            'category_index.id': categoryId
          };
        }),
        where: products.defaultQuery(req),
      });
    });
  });
};

// Get child category IDs recursively
categories.getAllChildIdsRecursive = (schema, req, parentIds, ids) => {
  ids = ids || [];
  return schema.get('/categories', {
    id: { $in: parentIds },
    fields: 'id',
    where: categories.defaultQuery(req),
    sort: categories.defaultSort(req),
    include: {
      children: {
        url: '/categories',
        params: {
          parent_id: 'id'
        },
        data: {
          fields: 'id',
          limit: null
        }
      }
    }
  }).then(result => {
    if (!result || !result.results.length) {
      return ids;
    }
    return Promise.all(
      result.results.map(category => {
        ids.push(category.id);
        if (category.children.results.length) {
          const nextIds = category.children.results.map(child => child.id);
          return new Promise(resolve => {
            categories.getAllChildIdsRecursive(schema, req, nextIds, ids).then(resolve);
          });
        }
      })
    ).then(() => {
      return ids;
    });
  });
};

// Get a category

// Default query
categories.defaultQuery = (req) => {
  return {
    active: true,
    navigation: req.query.navigation || undefined,
  }
};

// Default sort
categories.defaultSort = (req) => {
  return {
    sort: 'sort ascending',
  }
};

// Filter include query for categories
categories.filterIncludeQuery = (req) => {
  let include = {};
  if (req.query.depth) {
    include = categories.filterIncludeQueryDepth(include, req);
  }
  return include;
};

// Filter depth query to include nested child categories
categories.filterIncludeQueryDepth = (include, req) => {
  // Depth query specifies how deep the children should fetch
  let depth = req.query.depth;
  if (depth > 1) {
    if (depth > 5) {
      throw util.error(400, 'Too much depth');
    }
    let deepInclude = include;
    while (--depth) {
      deepInclude.children = {};
      deepInclude.children.url = '/categories',
      deepInclude.children.params = { parent_id: 'id' };
      deepInclude.children.data = { include: {} };
      // Recursion here
      deepInclude = deepInclude.children.data.include;
    }
  }
  return include;
};
<|endoftext|>


<commit_before>/**
 * Heldesks' code (Zendesk, etc..)
 */

App.helpdesk = {
    init: function () {
        // fetch template content from the extension
        if (window.location.hostname.indexOf('zendesk.com') !== -1) {
            App.helpdesk.zendesk.init();
        }
    },
    zendesk: {
        init: function () {
            // inject the zendesk script into the dom
            var script = document.createElement('script');
            script.type = "text/javascript";
            script.src = chrome.extension.getURL("pages/helpdesk/zendesk.js");
            if (document.body) {
                document.body.appendChild(script);
                script.onload = function () {
                    document.body.removeChild(script);
                };
            }
            var ticketUrl = "";
            var ticketInterval = setInterval(function () {
                if (window.location.pathname.indexOf('/agent/tickets/') !== -1) {
                    if (!ticketUrl || ticketUrl !== window.location.pathname) {
                        ticketUrl = window.location.pathname;

                        $('.macro-suggestions-container').remove();

                        var bodyInterval = setInterval(function () {
                            var subject = '';
                            var body = '';

                            $('.workspace').each(function (i, workspace) {
                                workspace = $(workspace);

                                if (workspace.css('display') !== 'none') {
                                    var firstEvent = workspace.find('.event-container .event:first');
                                    var isAgent = firstEvent.find('.user_photo').hasClass('agent');

                                    // If it's an agent who has the last comment no point in suggesting anything
                                    if (isAgent) {
                                        return false;
                                    }

                                    subject = workspace.find('input[name=subject]').val();
                                    body = firstEvent.find('.zd-comment').text();
                                }
                            });

                            if (!body.length) {
                                return;
                            }

                            clearInterval(bodyInterval);

                            chrome.runtime.sendMessage({
                                'request': 'suggestion',
                                'data': {
                                    'agent': {
                                        'host': window.location.host,
                                        'name': $('#face_box .name').text()
                                    },
                                    'subject': subject,
                                    'to': '',
                                    'cc': '',
                                    'bcc': '',
                                    'from': '',
                                    'body': body,
                                    'helpdesk': 'zendesk'
                                }
                            }, function (macros) {
                                if (!_.size(macros)) {
                                    return;
                                }

                                $('.macro-suggestions-container').remove();
                                var macroContainer = $("
");

                                for (var i in macros) {
                                    var macro = macros[i];
                                    var macroBtn = $("");
                                    var macroEl = $("");
                                    var scoreEl = $(' ');
                                    if (macro.score >= 0.8) {
                                        scoreEl.addClass('macro-score-high');
                                    }
                                    if (macro.score >= 0.6 && macro.score < 0.8) {
                                        scoreEl.addClass('macro-score-medium');
                                    }
                                    if (macro.score < 0.6) {
                                        scoreEl.addClass('macro-score-low');
                                    }

                                    macroEl.attr('onclick', "gorgiasApplyMacroSuggestion(" + macro["external_id"] + ")");
                                    macroBtn.attr('title', macro.body.replace(/\n/g, "
"));
                                    macroBtn.attr('data-toggle', "tooltip");
                                    macroBtn.attr('data-html', "true");
                                    macroBtn.attr('data-placement', "bottom");
                                    macroEl.html(macro.title);

                                    macroBtn.append(macroEl);
                                    macroBtn.append(scoreEl);
                                    macroContainer.append(macroBtn);
                                }
                                $('.comment_input .content .options').before(macroContainer);
                            });
                        }, 100);
                    }
                } else {
                    ticketUrl = "";
                }
            }, 100);
        }
    }
};

<commit_msg>Wait until the subject is ready as well<commit_after>/**
 * Heldesks' code (Zendesk, etc..)
 */

App.helpdesk = {
    init: function () {
        // fetch template content from the extension
        if (window.location.hostname.indexOf('zendesk.com') !== -1) {
            App.helpdesk.zendesk.init();
        }
    },
    zendesk: {
        init: function () {
            // inject the zendesk script into the dom
            var script = document.createElement('script');
            script.type = "text/javascript";
            script.src = chrome.extension.getURL("pages/helpdesk/zendesk.js");
            if (document.body) {
                document.body.appendChild(script);
                script.onload = function () {
                    document.body.removeChild(script);
                };
            }
            var ticketUrl = "";
            var ticketInterval = setInterval(function () {
                if (window.location.pathname.indexOf('/agent/tickets/') !== -1) {
                    if (!ticketUrl || ticketUrl !== window.location.pathname) {
                        ticketUrl = window.location.pathname;

                        $('.macro-suggestions-container').remove();

                        var bodyInterval = setInterval(function () {
                            var subject = '';
                            var body = '';

                            $('.workspace').each(function (i, workspace) {
                                workspace = $(workspace);

                                if (workspace.css('display') !== 'none') {
                                    var firstEvent = workspace.find('.event-container .event:first');
                                    var isAgent = firstEvent.find('.user_photo').hasClass('agent');

                                    // If it's an agent who has the last comment no point in suggesting anything
                                    if (isAgent) {
                                        return false;
                                    }

                                    subject = workspace.find('input[name=subject]').val();
                                    body = firstEvent.find('.zd-comment').text();
                                }
                            });

                            if (!subject.length || !body.length) {
                                return;
                            }

                            clearInterval(bodyInterval);

                            chrome.runtime.sendMessage({
                                'request': 'suggestion',
                                'data': {
                                    'agent': {
                                        'host': window.location.host,
                                        'name': $('#face_box .name').text()
                                    },
                                    'subject': subject,
                                    'to': '',
                                    'cc': '',
                                    'bcc': '',
                                    'from': '',
                                    'body': body,
                                    'helpdesk': 'zendesk'
                                }
                            }, function (macros) {
                                if (!_.size(macros)) {
                                    return;
                                }

                                $('.macro-suggestions-container').remove();
                                var macroContainer = $("
");

                                for (var i in macros) {
                                    var macro = macros[i];
                                    var macroBtn = $("");
                                    var macroEl = $("");
                                    var scoreEl = $(' ');
                                    if (macro.score >= 0.8) {
                                        scoreEl.addClass('macro-score-high');
                                    }
                                    if (macro.score >= 0.6 && macro.score < 0.8) {
                                        scoreEl.addClass('macro-score-medium');
                                    }
                                    if (macro.score < 0.6) {
                                        scoreEl.addClass('macro-score-low');
                                    }

                                    macroEl.attr('onclick', "gorgiasApplyMacroSuggestion(" + macro["external_id"] + ")");
                                    macroBtn.attr('title', macro.body.replace(/\n/g, "
"));
                                    macroBtn.attr('data-toggle', "tooltip");
                                    macroBtn.attr('data-html', "true");
                                    macroBtn.attr('data-placement', "bottom");
                                    macroEl.html(macro.title);

                                    macroBtn.append(macroEl);
                                    macroBtn.append(scoreEl);
                                    macroContainer.append(macroBtn);
                                }
                                $('.comment_input .content .options').before(macroContainer);
                            });
                        }, 100);
                    }
                } else {
                    ticketUrl = "";
                }
            }, 100);
        }
    }
};

<|endoftext|>


<commit_before>/**
Editable text slots
@class
@name chunkText
@extends $.ui.chunk
@memberOf $.ui
*/
$.widget('ui.chunkText', $.ui.chunk,

	/**
	@lends $.ui.chunkText
	*/
	{

	title : '',

	content : '',

	_create : function() {
		var element = this.element.find('.chunk-text');
		this.element = (element.length)? $(element[0]) : this.element;

		$.ui.chunk.prototype._create.call(this);
	},

	bind : function() {
		var element = this.element;

		$.ui.chunk.prototype.bind.call(this);

		this.element
			.on('click', function() {
				element.focus();
			})
			.on('blur', function() {
				$('body').editor('blur', element);
			});
	},

	/**
	Make the element editable by invokeing boom.editor.edit() on it.
	*/
	edit : function(){

		var self = this;

		$.boom.log('Text chunk slot edit');

		var edit_content = function( $element ) {

			$element[0].id = $element[0].id || $.boom.util.dom.uniqueId('boom-dom-wysiwyg-');

			var old_html = self.element.html();

			if ($element.text() == 'Default text.') {
				$element.html( '' );
			}

			$element.unbind('keydown');

			$('body').editor('edit', $element)
				.fail( function(){
					self.element.html( old_html ).show();
					self.destroy();
				})
				.done(function() {
					var edited = old_html != $element.html();

					if ($element.text() == '') {
						self.remove();
					} else if (edited == true) {
						self._save();
					} else {
						self.bind();
					}
				});
		};

		edit_content(this.element);
	},

	/**
	Get the chunk HTML, escaped and cleaned.
	*/
	getData : function(){
		var $content = this.element.find('.slot-content');

		if ($content.length) {
			this.content = $content.html();
			this.title = this.element.find('.slot-title').text();
		} else {
			this.title = null;
			this.content = this.element.html();
		}

		return {
			title : this.title,
			text : this.content,
			is_block : this.isBlockLevel()? 1 : 0
		};
	},

	isBlockLevel : function() {
		return this.element.is('div');
	}
});<commit_msg>Removed references to text chunk titles<commit_after>/**
Editable text slots
@class
@name chunkText
@extends $.ui.chunk
@memberOf $.ui
*/
$.widget('ui.chunkText', $.ui.chunk,

	/**
	@lends $.ui.chunkText
	*/
	{

	content : '',

	_create : function() {
		var element = this.element.find('.chunk-text');
		this.element = (element.length)? $(element[0]) : this.element;

		$.ui.chunk.prototype._create.call(this);
	},

	bind : function() {
		var element = this.element;

		$.ui.chunk.prototype.bind.call(this);

		this.element
			.on('click', function() {
				element.focus();
			})
			.on('blur', function() {
				$('body').editor('blur', element);
			});
	},

	/**
	Make the element editable by invokeing boom.editor.edit() on it.
	*/
	edit : function(){

		var self = this;

		$.boom.log('Text chunk slot edit');

		var edit_content = function( $element ) {

			$element[0].id = $element[0].id || $.boom.util.dom.uniqueId('boom-dom-wysiwyg-');

			var old_html = self.element.html();

			if ($element.text() == 'Default text.') {
				$element.html( '' );
			}

			$element.unbind('keydown');

			$('body').editor('edit', $element)
				.fail( function(){
					self.element.html( old_html ).show();
					self.destroy();
				})
				.done(function() {
					var edited = old_html != $element.html();

					if ($element.text() == '') {
						self.remove();
					} else if (edited == true) {
						self._save();
					} else {
						self.bind();
					}
				});
		};

		edit_content(this.element);
	},

	/**
	Get the chunk HTML, escaped and cleaned.
	*/
	getData : function(){
		var $content = this.element.find('.slot-content');

		this.content = ($content.length)? $content.html() : this.element.html();

		return {
			text : this.content,
			is_block : this.isBlockLevel()? 1 : 0
		};
	},

	isBlockLevel : function() {
		return this.element.is('div');
	}
});<|endoftext|>


<commit_before>window.IntegrationTestConfig = {
	host: 'http://localhost:8181/launcher/',
	featurePath: 'out/feature.feature',
	stepDefinitionsPath: 'out/step_definitions.js'
};<commit_msg>fix integration tests config<commit_after>window.IntegrationTestConfig = {
	host: 'http://localhost:8181/',
	featurePath: 'out/feature.feature',
	stepDefinitionsPath: 'out/step_definitions.js'
};<|endoftext|>


<commit_before>import React, { Component } from "react";
import numeral from "numeral";
import {Link} from 'react-router-dom';
import PropTypes from 'prop-types';

class HostMenu extends Component {

  state = {
    toggled: false
  };

  toggle = () => {
    const { toggled } = this.state;
    this.setState({
      toggled: !toggled
    });
  };

  render() {
    let data = this.props.data;

    if(!data || !data.total){
      data = {
        down: {
          total: null,
          unhandled: null
        },
        unreachable: {
          total: null,
          unhandled: null
        },
        ok: null,
        pending: null,
        total: 0,
      }
    }

    let { down, unreachable, ok, pending, total } = data;

    const { toggled } = this.state;

    return (

            {pending > 0 ?  : null}

          Hosts

        <Link to="./main.php?p=20202&o=h_down&search=" class={"wrap-middle-icon round round-small "+ (down.unhandled > 0 ? "red" : "red-bordered")}>

            {numeral(down.unhandled).format("0a")}

        </Link>
        <Link to="./main.php?p=20202&o=h_unreachable&search=" class={"wrap-middle-icon round round-small "+ (unreachable.unhandled > 0 ? "gray-dark" : "gray-dark-bordered")}>

            {numeral(unreachable.unhandled).format("0a")}

        </Link>
        <Link to="./main.php?p=20202&o=h_up&search=" class={"wrap-middle-icon round round-small "+ (ok > 0 ? "green" : "green-bordered")}>

            {numeral(ok).format("0a")}

        </Link>

        {this.props.children}

                <Link
                  to={"./main.php?p=20202&o=h&search="}
                  class="submenu-item-link"
                >
                  All
                  {numeral(total).format("0a")}
                </Link>

                <Link
                  to={"./main.php?p=20202&o=h_down&search="}
                  class="submenu-item-link"
                >
                  Down

                    {numeral(down.unhandled).format("0a")}/{numeral(down.total).format("0a")}

                </Link>

                <Link
                  to={"./main.php?p=20202&o=h_unreachable&search="}
                  class="submenu-item-link"
                >
                  Unreachable

                    {numeral(unreachable.unhandled).format("0a")}/{numeral(unreachable.total).format("0a")}

                </Link>

                <Link
                  to={"./main.php?p=20202&o=h_up&search="}
                  class="submenu-item-link"
                >
                  Ok
                  {numeral(ok).format("0a")}
                </Link>

                <Link
                  to={"./main.php?p=20202&o=h_pending&search="}
                  class="submenu-item-link"
                >
                  Pending
                  {numeral(pending).format("0a")}
                </Link>

    );
  }
}

export default HostMenu;
HostMenu.propTypes = {
  children: PropTypes.element.isRequired,
};
<commit_msg>fix(ux): wording issue in header menu host's drop-down (#6686)<commit_after>import React, { Component } from "react";
import numeral from "numeral";
import {Link} from 'react-router-dom';
import PropTypes from 'prop-types';

class HostMenu extends Component {

  state = {
    toggled: false
  };

  toggle = () => {
    const { toggled } = this.state;
    this.setState({
      toggled: !toggled
    });
  };

  render() {
    let data = this.props.data;

    if(!data || !data.total){
      data = {
        down: {
          total: null,
          unhandled: null
        },
        unreachable: {
          total: null,
          unhandled: null
        },
        ok: null,
        pending: null,
        total: 0,
      }
    }

    let { down, unreachable, ok, pending, total } = data;

    const { toggled } = this.state;

    return (

            {pending > 0 ?  : null}

          Hosts

        <Link to="./main.php?p=20202&o=h_down&search=" class={"wrap-middle-icon round round-small "+ (down.unhandled > 0 ? "red" : "red-bordered")}>

            {numeral(down.unhandled).format("0a")}

        </Link>
        <Link to="./main.php?p=20202&o=h_unreachable&search=" class={"wrap-middle-icon round round-small "+ (unreachable.unhandled > 0 ? "gray-dark" : "gray-dark-bordered")}>

            {numeral(unreachable.unhandled).format("0a")}

        </Link>
        <Link to="./main.php?p=20202&o=h_up&search=" class={"wrap-middle-icon round round-small "+ (ok > 0 ? "green" : "green-bordered")}>

            {numeral(ok).format("0a")}

        </Link>

        {this.props.children}

                <Link
                  to={"./main.php?p=20202&o=h&search="}
                  class="submenu-item-link"
                >
                  All
                  {numeral(total).format("0a")}
                </Link>

                <Link
                  to={"./main.php?p=20202&o=h_down&search="}
                  class="submenu-item-link"
                >
                  Down

                    {numeral(down.unhandled).format("0a")}/{numeral(down.total).format("0a")}

                </Link>

                <Link
                  to={"./main.php?p=20202&o=h_unreachable&search="}
                  class="submenu-item-link"
                >
                  Unreachable

                    {numeral(unreachable.unhandled).format("0a")}/{numeral(unreachable.total).format("0a")}

                </Link>

                <Link
                  to={"./main.php?p=20202&o=h_up&search="}
                  class="submenu-item-link"
                >
                  Up
                  {numeral(ok).format("0a")}
                </Link>

                <Link
                  to={"./main.php?p=20202&o=h_pending&search="}
                  class="submenu-item-link"
                >
                  Pending
                  {numeral(pending).format("0a")}
                </Link>

    );
  }
}

export default HostMenu;
HostMenu.propTypes = {
  children: PropTypes.element.isRequired,
};
<|endoftext|>


<commit_before>import React from 'react'

class ProjectRadioGroups extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
         selectedProject: undefined,
         projectList: []
    }
  }

  addToProjectList(project) {
      this.setState({selectedProject: project.value, projectList: this.state.projectList.concat([project])});
  }

render() {
    console.log(this.state);

    // Group project after client id
    var groupedProjects = [];
    var that = this;
    this.state.projectList.forEach(function(project){
        if(groupedProjects[project.client] == null)
            groupedProjects[project.client] = [];

          groupedProjects[project.client].push(project);
    });

    //TODO: Fix sort
    groupedProjects.sort();

    var buttonGroups = Object.keys(groupedProjects).map(function(client) {
        var projectButtons = groupedProjects[client].map(function(project) {
            var cls = "btn btn-secondary";
            if(project.value == that.state.selectedProject) {
                cls += " active"
            }
            return (<button type="button" className={cls}>{project.label}</button>)
        });

        return (

{client}

                    {projectButtons}

        );
        });

      return(

                {buttonGroups}

      );
  }
}

var dotStyle = {
    width: '10px',
    height: '10px',
    backgroundColor: '#F0F'
}

export default ProjectRadioGroups
<commit_msg>Build project buttons correctly and with style<commit_after>import React from 'react'

class ProjectRadioGroups extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
         selectedProject: undefined,
         projectList: []
    }
  }

  addToProjectList(project) {
      var projectList = this.state.projectList;
      if(projectList.indexOf(project) == -1)
        projectList = this.state.projectList.concat([project])

      this.setState({selectedProject: project.value, projectList: projectList});
  }

  selectProject(project) {
      console.log("Select!");
      this.setState({selectedProject: project.value});
  }

  createDotStyle(project) {

    var dotStyle = {
        width: '13px',
        height: '13px',
        display: 'inline-block',
        marginRight: '5px',
        backgroundColor: project.color
    }
    return dotStyle;
  }

render() {
    console.log(this.state);

    // Group project after client id
    var groupedProjects = [];
    var that = this;
    this.state.projectList.forEach(function(project){
        if(groupedProjects[project.client] == null)
            groupedProjects[project.client] = [];

          groupedProjects[project.client].push(project);
    });

    //TODO: Fix sort
    groupedProjects.sort();

    var buttonGroups = Object.keys(groupedProjects).map(function(client) {
        var projectButtons = groupedProjects[client].map(function(project) {
            var cls = "btn btn-secondary";
            if(project.value == that.state.selectedProject) {
                cls += " active"
            }
            return (<button key={project.value} onClick={() => that.selectProject(project)} type="button" className={cls}>{project.label}</button>)
        });

        return (

{client}

                    {projectButtons}

        );
        });

      return(

                {buttonGroups}

      );
  }
}

export default ProjectRadioGroups
<|endoftext|>


<commit_before>var StereoConvergence = function(player){
	var _this      = this;
	this.player    = player;
	this.eyeLeft   = this.player.querySelector('[data-eye="left"]');
	this.eyeRight  = this.player.querySelector('[data-eye="right"]');
	this.outputs   = {
		min : parseFloat(this.player.getAttribute("data-stereo-min")) || -1,
		max : parseFloat(this.player.getAttribute("data-stereo-max")) || 1
	};
	this.clip      = player.getAttribute("data-stereo-clip") || true;
	this.inputs    = {
		max : 1,
		min : 0
	};

	// Initialization
	this.init = function () {
		_this.bindEvents();

		// Clip images if instructed
		if ( this.clip == true ) {
			this.clipImage();
		}

		_this.getDimensions();
		window.onresize = _this.getDimensions();
	};

	this.handleClick = function (event) {
		// prevent scrolling for touch
		event.preventDefault();

		moveEvent = (event.touches ? event.touches[0] : event);

		// use single touch as event
		_this.setPositions(moveEvent);
	}
	// Event Binding
	this.bindEvents = function () {
		this.player.addEventListener('mousemove', _this.handleClick, false);
		this.player.addEventListener('touchmove', _this.handleClick, false);
	};

	// Event Unbinding
	this.destroy = function () {
		this.player.removeEventListener('mousemove', _this.handleClick);
		this.player.removeEventListener('touchmove', _this.handleClick);
	};

	// Image Overflow Clipping
	this.clipImage = function (){
		var percentageMax         = Math.max( Math.abs(this.outputs.min), Math.abs(this.outputs.max) );
		var clipMargin            = ( -1 * percentageMax ) + "%";
		var clipWidth             = ( percentageMax * 2 + 100) + "%";
		this.eyeLeft.style.width       = clipWidth;
		this.eyeLeft.style.marginLeft  = clipMargin;
		this.eyeRight.style.width      = clipWidth;
		this.eyeRight.style.marginLeft = clipMargin;
	};

	// Calculate Viewer Size and Position
	this.getDimensions = function (){
		_this.playerHeight  = _this.player.clientHeight;
		_this.playerBox     = _this.player.getBoundingClientRect();
		_this.playerOffset  = {
			top: _this.playerBox.top + document.body.scrollTop,
			left: _this.playerBox.left + document.body.scrollLeft
		};
	};

	// Set Positions
	this.setPositions = function (event){
		if (_this.playerHeight !== _this.player.clientHeight) {
			_this.getDimensions();
			return;
		}
		// get position
		var mY         = event.pageY - _this.playerOffset.top;
		// convert to percentage of image height and clip to input min / max for touch dragging
		var yAdjusted  = Math.max( Math.min( (mY / _this.playerHeight), _this.inputs.max ), _this.inputs.min);
		// convert to user-set range
		var yConverted = _this.convert(yAdjusted);
		// move eyes in opposite directions
		_this.eyeLeft.style.transform  = "translateX(" + (-1 * yConverted) + "%)";
		_this.eyeRight.style.transform = "translateX(" + (yConverted) + "%)";
	};

	// Convert Pointer Values to Desired CSS Offsets
	this.convert = function (input){
		var	inputRange  = (_this.inputs.max - _this.inputs.min);
		var	outputRange = ( _this.outputs.max - _this.outputs.min );
		var value       = ((input - _this.inputs.min) / (_this.inputs.max - _this.inputs.min)) * (_this.outputs.max - _this.outputs.min) + _this.outputs.min;
		return value;
	};

};

var players   = document.querySelectorAll('[data-stereoplayer]');
var instances = [];

// Loop over [data-stereoplayer] instances and initialize each as a stereoscopic viewer
for (var i = 0; i < players.length; ++i) {
	var player = players[i];
	instances[i] = new StereoConvergence(player).init();

}
<commit_msg>allow config via js object<commit_after>var StereoConvergence = function(options){
	var _this            = this,
	    eyeLeftSelector  = options.eyeLeftSelector || '[data-eye="left"]',
	    eyeRightSelector = options.eyeLeftSelector || '[data-eye="right"]';

	this.player          = options.player;
	this.eyeLeft         = this.player.querySelector(eyeLeftSelector);
	this.eyeRight        = this.player.querySelector(eyeRightSelector);
	this.outputs         = {
		min : parseFloat(this.player.getAttribute("data-stereo-min")) || -1,
		max : parseFloat(this.player.getAttribute("data-stereo-max")) || 1
	};
	this.clip            = options.clip || player.getAttribute("data-stereo-clip") || true;
	this.inputs          = {
		max : 1,
		min : 0
	};

	// Initialization
	this.init = function () {
		_this.bindEvents();

		// Clip images if instructed
		if ( this.clip == true ) {
			this.clipImage();
		}

		_this.getDimensions();
		window.addEventListener('resize', _this.getDimensions);
	};

	this.handleClick = function (event) {
		// prevent scrolling for touch
		event.preventDefault();

		moveEvent = (event.touches ? event.touches[0] : event);

		// use single touch as event
		_this.setPositions(moveEvent);
	}
	// Event Binding
	this.bindEvents = function () {
		this.player.addEventListener('mousemove', _this.handleClick, false);
		this.player.addEventListener('touchmove', _this.handleClick, false);
	};

	// Event Unbinding
	this.destroy = function () {
		this.player.removeEventListener('mousemove', _this.handleClick);
		this.player.removeEventListener('touchmove', _this.handleClick);
	};

	// Image Overflow Clipping
	this.clipImage = function (){
		var percentageMax         = Math.max( Math.abs(this.outputs.min), Math.abs(this.outputs.max) );
		var clipMargin            = ( -1 * percentageMax ) + "%";
		var clipWidth             = ( percentageMax * 2 + 100) + "%";
		this.eyeLeft.style.width       = clipWidth;
		this.eyeLeft.style.marginLeft  = clipMargin;
		this.eyeRight.style.width      = clipWidth;
		this.eyeRight.style.marginLeft = clipMargin;
	};

	// Calculate Viewer Size and Position
	this.getDimensions = function (){
		_this.playerHeight  = _this.player.clientHeight;
		_this.playerBox     = _this.player.getBoundingClientRect();
		_this.playerOffset  = {
			top: _this.playerBox.top + document.body.scrollTop,
			left: _this.playerBox.left + document.body.scrollLeft
		};
	};

	// Set Positions
	this.setPositions = function (event){
		if (_this.playerHeight !== _this.player.clientHeight) {
			_this.getDimensions();
			return;
		}
		// get position
		var mY         = event.pageY - _this.playerOffset.top;
		// convert to percentage of image height and clip to input min / max for touch dragging
		var yAdjusted  = Math.max( Math.min( (mY / _this.playerHeight), _this.inputs.max ), _this.inputs.min);
		// convert to user-set range
		var yConverted = _this.convert(yAdjusted);
		// move eyes in opposite directions
		console.log(yConverted)
		_this.eyeLeft.style.transform  = "translateX(" + (-1 * yConverted) + "%)";
		_this.eyeRight.style.transform = "translateX(" + (yConverted) + "%)";
	};

	// Convert Pointer Values to Desired CSS Offsets
	this.convert = function (input){
		var	inputRange  = (_this.inputs.max - _this.inputs.min);
		var	outputRange = ( _this.outputs.max - _this.outputs.min );
		var value       = ((input - _this.inputs.min) / (_this.inputs.max - _this.inputs.min)) * (_this.outputs.max - _this.outputs.min) + _this.outputs.min;
		return value;
	};

};

var players   = document.querySelectorAll('[data-stereoplayer]');
var instances = [];

// Loop over [data-stereoplayer] instances and initialize each as a stereoscopic viewer
for (var i = 0; i < players.length; ++i) {
	var player = players[i];
	instances[i] = new StereoConvergence({player: player}).init();

}
<|endoftext|>


<commit_before>var scripts = document.getElementsByTagName('script'),
    script = scripts[scripts.length - 1];

var get_time_display = function(stamp) {
    var dt = new Date(stamp);
    var period = dt.getHours() < 12 ? 'AM' : 'PM';
    var hours = dt.getHours() > 12 ? dt.getHours() - 12 : dt.getHours();
    if (hours === 0) {
        hours = 12;
    }
    var minutes = dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes();
    return hours + ':' + minutes + ' ' + period;
};

var get_hms_display_from_seconds = function(total_seconds) {

    var hours = Math.floor(total_seconds / (60 * 60));
    var minutes = Math.floor((total_seconds % 360) / 60);
    var seconds = total_seconds % 360 % 60;

    var get_hms_text = function(hours, minutes, seconds) {
        var result = '';

        var extend_message = function(unit, unit_description) {
            var msg = '';
            if (unit > 0) {
                msg += unit + ' ' + unit_description;
                msg += unit !== 1 ? 's ' : ' ';
            }
            return msg;
        };

        result += extend_message(hours, 'hour');
        result += extend_message(minutes, 'minute');
        result += extend_message(seconds, 'second');
        return result;
    };
    return get_hms_text(hours, minutes, seconds);
};

function Timeline(loc, start_time, end_time, width, height) {
    this.loc = loc;
    this.width = width;
    this.height = height;

    this.container = d3.select(this.loc).append('svg');

    // Settings
    var today = new Date();
    today.setHours(0),
    today.setMinutes(0),
    today.setSeconds(0),
    today.setMilliseconds(0);
    today = today.getTime();
    this.draw_width = this.width - 40; // Draw inside the svg without clipping
    this.y_offset = 20; // Height offset from time marker
    var one_hour = 60 * 60 * 1000; // One hour in milliseconds
    var default_start = (one_hour * 8) + today;
        default_end = (one_hour * 18) + today;
    this.interval = one_hour;

    /*
    Set the start and end times to defaults (8AM - 6PM) unless entries fall
    outside of this, in which case round to the nearest hour on the outside.
    */
    if (start_time < default_start) {
        this.start_time = Math.floor(start_time / one_hour) * one_hour;
    } else {
        this.start_time = default_start;
    }

    if (end_time > default_end) {
        this.end_time = Math.ceil(end_time / one_hour) * one_hour;
    } else {
        this.end_time = default_end;
    }

    var span_in_hours = (this.end_time - this.start_time) / one_hour,
        width_span_ratio = Math.ceil(this.draw_width / span_in_hours);
    /*
    If the browser is small enough or more than a day has elapsed, show six
    time interval markers
    */
    if (width_span_ratio < 60 || span_in_hours > 24) {
        this.interval = Math.floor((span_in_hours * one_hour) / 5);
    }
    this.edge_offset = 20; // Come off the edge
}

Timeline.prototype.draw = function() {
    var dt = this.end_time - this.start_time;

    this.line_offset = this.draw_width / (dt / this.interval);

    for(var i = 0; i <= dt / this.interval; i++) {
        var x_offset = i * this.line_offset + this.edge_offset;

        this.container.append('line')
            .attr('x1', x_offset).attr('x2', x_offset)
            .attr('y1', this.y_offset).attr('y2', this.height - this.y_offset)
            .style('stroke', '#000000');

        var time = get_time_display(new Date(this.start_time + this.interval * i));

        this.container.append('text')
            .attr('x', x_offset - 15).attr('y', 10)
            .style('font-size', '10px')
            .style('fill', '#000000')
            .style('opacity', 1)
            .text(time);
    }
};

function Entry(timeline, entry_data) {
    this.project_name = entry_data['project_name'];
    this.start_time = entry_data['start_time'];
    this.end_time = entry_data['end_time'];
    this.hours = entry_data['hours'];
    this.pk = entry_data['pk'];
    this.update_url = entry_data['update_url'];
    this.active = entry_data['active'];
    this.seconds_paused = entry_data['seconds_paused'];

    this.timeline = timeline;

    this.height = timeline.height - 2 * timeline.y_offset;
}

Entry.prototype.draw = function() {
    var timeline = this.timeline;

    var dt = this.end_time - this.start_time,
        width = timeline.line_offset * (dt / timeline.interval),
        x_offset = timeline.line_offset *
            (this.start_time - timeline.start_time) / timeline.interval  + timeline.edge_offset;

    var entry = timeline.container.append('rect')
        .attr('x', x_offset).attr('y', timeline.y_offset + 5)
        .attr('rx', 10).attr('ry', 10)
        .attr('height', this.height - 10)
        .attr('width', width);

    // Add the attributes for popovers
    var popover_msg = 'You have worked ' + this.hours + ' hours';
    if (this.seconds_paused > 0) {
        var hms = get_hms_display_from_seconds(this.seconds_paused);
        popover_msg += ' with ' + hms + ' paused';
    }
    var start_str = get_time_display(new Date(this.start_time));
    var end_str = get_time_display(new Date(this.end_time));
    popover_msg += '
' + start_str + ' - ' + end_str;
    entry.attr('data-title', this.project_name)
        .attr('data-content', popover_msg);

    entry.transition()
        .delay(100)
        .duration(1500)
        .attr('fill', '#0061AA')
        .attr('stroke', '#333333')
        .attr('stroke-width', '2px')
        .style('opacity', '0.95');

    var text = timeline.container.append('text')
        .attr('font-weight', 'bolder')
        .style('font-size', '14px')
        .text(this.project_name);

    // Get bounding boxes for centering text
    var text_box = text.node().getBBox(),
        entry_box = entry.node().getBBox();

    // Strip project name down if its too large
    if(text_box.width > entry_box.width) {
        var sub = Math.ceil(text_box.width / (text_box.width - entry_box.width)), // Ratio using widths
            name = this.project_name.substring(0, sub) + '...';

        name = sub === 0 || entry_box.width < 30 ? '' : name;

        text.text(name);

        // New bounding box
        text_box = text.node().getBBox();
    }

    // Do some positioning to get the text centered correctly
    var text_xpos = x_offset + width / 2 - text_box.width / 2,
        text_ypos = timeline.y_offset + this.height / 2 + text_box.height / 4;

    text.attr('x', text_xpos)
        .attr('y', text_ypos);

    // Display!
    text.transition()
        .delay(100)
        .duration(1500)
        .attr('fill', '#FFFFFF')
        .style('opacity', '1.0');

    // Handle click
    var that = this;
    $(entry.node()).click(function() {
        location.href = that.update_url;
    });
};

var draw_timeline = function(data) {
    var $timeline = $('#timeline'),
        height = $timeline.height(),
        width = $(window).width();

    var start_time = new Date(data.start_time),
        end_time = new Date(data.end_time);

    // Convert the minutes to milliseconds
    var timezone_offset = start_time.getTimezoneOffset() * 60 * 1000;

    var t = new Timeline($timeline.selector, start_time.getTime() + timezone_offset,
        end_time.getTime() + timezone_offset, width, height);
    t.draw();

    for(var i = 0; i < data.entries.length; i++) {
        var entry = data.entries[i],
            start = new Date(entry.start_time),
            end = new Date(entry.end_time);

        var dt = end - start,
            hours = Math.ceil((dt / (3600 * 1000)) * 100) / 100;

        var e = new Entry(t, {
            'start_time': start.getTime() + timezone_offset,
            'end_time': end.getTime() + timezone_offset,
            'project_name': entry.project,
            'pk': entry.pk,
            'active': entry.active,
            'seconds_paused': entry.seconds_paused,
            'hours': hours,
            'update_url': entry.update_url
        });
        e.draw();
    }

    // Enable popovers
    $('.timeline rect').popover({
        'placement': 'top',
        'trigger': 'hover'
    });
};

(function() {
    var data = JSON.parse(script.getAttribute('data-entries'));
    draw_timeline(data);

    $(window).resize(function() {
        $('#timeline svg').remove();
        draw_timeline(data);
    });
})();
<commit_msg>Removed transitions from todays work chart<commit_after>var scripts = document.getElementsByTagName('script'),
    script = scripts[scripts.length - 1];

var get_time_display = function(stamp) {
    var dt = new Date(stamp);
    var period = dt.getHours() < 12 ? 'AM' : 'PM';
    var hours = dt.getHours() > 12 ? dt.getHours() - 12 : dt.getHours();
    if (hours === 0) {
        hours = 12;
    }
    var minutes = dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes();
    return hours + ':' + minutes + ' ' + period;
};

var get_hms_display_from_seconds = function(total_seconds) {

    var hours = Math.floor(total_seconds / (60 * 60));
    var minutes = Math.floor((total_seconds % 360) / 60);
    var seconds = total_seconds % 360 % 60;

    var get_hms_text = function(hours, minutes, seconds) {
        var result = '';

        var extend_message = function(unit, unit_description) {
            var msg = '';
            if (unit > 0) {
                msg += unit + ' ' + unit_description;
                msg += unit !== 1 ? 's ' : ' ';
            }
            return msg;
        };

        result += extend_message(hours, 'hour');
        result += extend_message(minutes, 'minute');
        result += extend_message(seconds, 'second');
        return result;
    };
    return get_hms_text(hours, minutes, seconds);
};

function Timeline(loc, start_time, end_time, width, height) {
    this.loc = loc;
    this.width = width;
    this.height = height;

    this.container = d3.select(this.loc).append('svg');

    // Settings
    var today = new Date();
    today.setHours(0),
    today.setMinutes(0),
    today.setSeconds(0),
    today.setMilliseconds(0);
    today = today.getTime();
    this.draw_width = this.width - 40; // Draw inside the svg without clipping
    this.y_offset = 20; // Height offset from time marker
    var one_hour = 60 * 60 * 1000; // One hour in milliseconds
    var default_start = (one_hour * 8) + today;
        default_end = (one_hour * 18) + today;
    this.interval = one_hour;

    /*
    Set the start and end times to defaults (8AM - 6PM) unless entries fall
    outside of this, in which case round to the nearest hour on the outside.
    */
    if (start_time < default_start) {
        this.start_time = Math.floor(start_time / one_hour) * one_hour;
    } else {
        this.start_time = default_start;
    }

    if (end_time > default_end) {
        this.end_time = Math.ceil(end_time / one_hour) * one_hour;
    } else {
        this.end_time = default_end;
    }

    var span_in_hours = (this.end_time - this.start_time) / one_hour,
        width_span_ratio = Math.ceil(this.draw_width / span_in_hours);
    /*
    If the browser is small enough or more than a day has elapsed, show six
    time interval markers
    */
    if (width_span_ratio < 60 || span_in_hours > 24) {
        this.interval = Math.floor((span_in_hours * one_hour) / 5);
    }
    this.edge_offset = 20; // Come off the edge
}

Timeline.prototype.draw = function() {
    var dt = this.end_time - this.start_time;

    this.line_offset = this.draw_width / (dt / this.interval);

    for(var i = 0; i <= dt / this.interval; i++) {
        var x_offset = i * this.line_offset + this.edge_offset;

        this.container.append('line')
            .attr('x1', x_offset).attr('x2', x_offset)
            .attr('y1', this.y_offset).attr('y2', this.height - this.y_offset)
            .style('stroke', '#000000');

        var time = get_time_display(new Date(this.start_time + this.interval * i));

        this.container.append('text')
            .attr('x', x_offset - 15).attr('y', 10)
            .style('font-size', '10px')
            .style('fill', '#000000')
            .style('opacity', 1)
            .text(time);
    }
};

function Entry(timeline, entry_data) {
    this.project_name = entry_data['project_name'];
    this.start_time = entry_data['start_time'];
    this.end_time = entry_data['end_time'];
    this.hours = entry_data['hours'];
    this.pk = entry_data['pk'];
    this.update_url = entry_data['update_url'];
    this.active = entry_data['active'];
    this.seconds_paused = entry_data['seconds_paused'];

    this.timeline = timeline;

    this.height = timeline.height - 2 * timeline.y_offset;
}

Entry.prototype.draw = function() {
    var timeline = this.timeline;

    var dt = this.end_time - this.start_time,
        width = timeline.line_offset * (dt / timeline.interval),
        x_offset = timeline.line_offset *
            (this.start_time - timeline.start_time) / timeline.interval  + timeline.edge_offset;

    var entry = timeline.container.append('rect')
        .attr('x', x_offset).attr('y', timeline.y_offset + 5)
        .attr('rx', 10).attr('ry', 10)
        .attr('height', this.height - 10)
        .attr('width', width);

    // Add the attributes for popovers
    var popover_msg = 'You have worked ' + this.hours + ' hours';
    if (this.seconds_paused > 0) {
        var hms = get_hms_display_from_seconds(this.seconds_paused);
        popover_msg += ' with ' + hms + ' paused';
    }
    var start_str = get_time_display(new Date(this.start_time));
    var end_str = get_time_display(new Date(this.end_time));
    popover_msg += '
' + start_str + ' - ' + end_str;
    entry.attr('data-title', this.project_name)
        .attr('data-content', popover_msg);

    entry.attr('fill', '#0061AA')
        .attr('stroke', '#333333')
        .attr('stroke-width', '2px')
        .style('opacity', '0.95');

    var text = timeline.container.append('text')
        .attr('font-weight', 'bolder')
        .style('font-size', '14px')
        .text(this.project_name);

    // Get bounding boxes for centering text
    var text_box = text.node().getBBox(),
        entry_box = entry.node().getBBox();

    // Strip project name down if its too large
    if(text_box.width > entry_box.width) {
        var sub = Math.ceil(text_box.width / (text_box.width - entry_box.width)), // Ratio using widths
            name = this.project_name.substring(0, sub) + '...';

        name = (sub === 0 || entry_box.width < 30) ? '' : name;
        text.text(name);

        // New bounding box
        text_box = text.node().getBBox();
    }

    // Do some positioning to get the text centered correctly
    var text_xpos = x_offset + width / 2 - text_box.width / 2,
        text_ypos = timeline.y_offset + this.height / 2 + text_box.height / 4;

    text.attr('x', text_xpos)
        .attr('y', text_ypos);

    // Display text
    text.attr('fill', '#FFFFFF')
        .style('opacity', '1.0');

    // Handle click
    var that = this;
    $(entry.node()).click(function() {
        location.href = that.update_url;
    });
};

var draw_timeline = function(data) {
    var $timeline = $('#timeline'),
        height = $timeline.height(),
        width = $(window).width();

    var start_time = new Date(data.start_time),
        end_time = new Date(data.end_time);

    // Convert the minutes to milliseconds
    var timezone_offset = start_time.getTimezoneOffset() * 60 * 1000;

    var t = new Timeline($timeline.selector, start_time.getTime() + timezone_offset,
        end_time.getTime() + timezone_offset, width, height);
    t.draw();

    for(var i = 0; i < data.entries.length; i++) {
        var entry = data.entries[i],
            start = new Date(entry.start_time),
            end = new Date(entry.end_time);

        var dt = end - start,
            hours = Math.ceil((dt / (3600 * 1000)) * 100) / 100;

        var e = new Entry(t, {
            'start_time': start.getTime() + timezone_offset,
            'end_time': end.getTime() + timezone_offset,
            'project_name': entry.project,
            'pk': entry.pk,
            'active': entry.active,
            'seconds_paused': entry.seconds_paused,
            'hours': hours,
            'update_url': entry.update_url
        });
        e.draw();
    }

    // Enable popovers
    $('.timeline rect').popover({
        'placement': 'top',
        'trigger': 'hover'
    });
};

(function() {
    var data = JSON.parse(script.getAttribute('data-entries'));
    draw_timeline(data);

    $(window).resize(function() {
        $('#timeline svg').remove();
        draw_timeline(data);
    });
})();
<|endoftext|>