<commit_before>

define('kbaseExpressionSampleTable',
    [
        'jquery',
        'kbwidget',
        'kbaseAuthenticatedWidget',
        'kbaseTabs',
        'kbaseBarchart',
        'kbase-client-api',
        'kbaseTable',
        'jquery-dataTables',
    ], function( $, colorbrewer ) {

    'use strict';

    $.KBWidget({

	    name: "kbaseExpressionSampleTable",
	    parent : "kbaseAuthenticatedWidget",

        version: "1.0.0",
        options: {
            numBins : 10,
        },

        _accessors : [
            {name: 'dataset', setter: 'setDataset'},
            {name: 'barchartDataset', setter: 'setBarchartDataset'},
        ],

        setDataset : function setDataset(newDataset) {

            var rows = [];
            var barData = [];

            var min = Number.MAX_VALUE;
            var max = Number.MIN_VALUE;

            var exprKeys = Object.keys(newDataset.expression_levels).sort();

            $.each(
                exprKeys,
                function (i,k) {

                    var val = newDataset.expression_levels[k];

                    rows.push( [k, val] );

                    if (val < min) {
                        min = val;
                    }
                    if (val > max) {
                        max = val;
                    }
                    barData.push(val);

                    /*var bin = Math.floor(newDataset.expression_levels[k]);

                    if (barData[bin] == undefined) {
                        barData[bin] = 0;
                    }
                    barData[bin]++;*/

                }
            );

            this.setBarchartDataset(barData);
            this.renderHistogram(this.options.numBins);

            var $dt = this.data('tableElem').dataTable({
                aoColumns : [
                    { title : 'Gene ID'},
                    { title : 'Feature Value'}
                ]
            });
            $dt.fnAddData(rows);

            this.data('loader').hide();
            this.data('containerElem').show();

        },

        renderHistogram : function renderHistogram(bins) {

            var barData = d3.layout.histogram().bins(bins)( this.barchartDataset() );

            var bars = [];
            var sigDigits = 1000;
            $.each(
                barData,
                function (i,bin) {
                    var range = Math.round(bin.x * sigDigits) / sigDigits + ' to ' + (Math.round((bin.x + bin.dx) * sigDigits) / sigDigits);

                    bars.push(
                        {
                            bar : range,
                            value : bin.y,
                            color : 'blue',
                            tooltip : bin.y + ' in range
' + range,
                        }
                    );
                }
            );

            this.data('barchart').setDataset(bars);

        },

        init : function init(options) {
            this._super(options);

            var $self = this;

            var ws = new Workspace(window.kbconfig.urls.workspace, {token : $self.authToken()});
            //var ws = new Workspace('https://ci.kbase.us/services/ws', {token : $self.authToken()});

            var ws_params = {
                workspace : this.options.workspace,
                wsid : this.options.wsid,
                name : this.options.output
            };

            ws.get_objects([ws_params]).then(function (d) {
                $self.setDataset(d[0].data);
            }).fail(function(d) {

                $self.$elem.empty();
                $self.$elem
                    .addClass('alert alert-danger')
                    .html("Could not load object : " + d.error.message);
            })

            this.appendUI(this.$elem);

            return this;
        },

        appendUI : function appendUI($elem) {

            var $me = this;

            var $tableElem = $.jqElem('table')
                .css('width', '95%')
                    .append(
                        $.jqElem('thead')
                            .append(
                                $.jqElem('tr')
                                    .append($.jqElem('th').append('Gene ID'))
                                    .append($.jqElem('th').append('Feature Value'))
                            )
                    )
            ;

            var $barElem = $.jqElem('div').css({width : 800, height : 500});

            var $barContainer = $.jqElem('div')
                .append(
                    $.jqElem('input')
                        .attr('type', 'range')
                        .attr('min', 0)
                        .attr('max', 100)
                        .attr('value', $me.options.numBins)
                        .attr('step', 1)
                        .css('width', '800px')
                        .on('input', function(e) {
                            $me.data('numBins').text($(this).val());
                        })
                        .on('change', function(e) {
                            $me.data('numBins').text($(this).val());
                            $me.renderHistogram(parseInt($(this).val()));
                        })
                )
                .append(
                    $.jqElem('span')
                        .attr('id', 'numBins')
                        .text($me.options.numBins)
                )
                .append(' bins
')
                .append($barElem)
            ;

            var $containerElem = $.jqElem('div').attr('id', 'containerElem').css('display', 'none');

            var $container = $containerElem.kbaseTabs(
                {
                    tabs : [
                        {
                            tab : 'Overview',
                            content : $tableElem
                        },
                        {
                            tab : 'Histogram',
                            content : $barContainer
                        }
                    ]
                }
            )

            $container.$elem.find('[data-tab=Histogram]').on('click', function(e) {
                $barchart.renderXAxis();
                setTimeout(function() {$me.renderHistogram($me.options.numBins) }, 300);
            });

            $elem
                .append( $containerElem )
                .append(
                    $.jqElem('div')
                        .attr('id', 'loader')
                        .append('
 Loading data...
 please wait...')
                        .append($.jqElem('br'))
                        .append(
                            $.jqElem('div')
                                .attr('align', 'center')
                                .append($.jqElem('i').addClass('fa fa-spinner').addClass('fa fa-spin fa fa-4x'))
                        )
                )
            ;

            var $barchart =
                $barElem.kbaseBarchart(
                    {
                        scaleAxes   : true,
                        xPadding : 60,
                        yPadding : 120,

                        xLabelRegion : 'yPadding',
                        yLabelRegion : 'xPadding',

                        xLabelOffset : 45,
                        yLabelOffset : -10,

                        yLabel : 'Number of Genes',
                        xLabel : 'Gene Expression Level log2(FPKM + 1)',
                        xAxisVerticalLabels : true

                    }
                )
            ;

            $barchart.superRenderXAxis = $barchart.renderXAxis;
            $barchart.renderXAxis = function() {
                $barchart.superRenderXAxis();

                $barchart.D3svg()
                    .selectAll('.xAxis .tick text')
                    .attr('fill', 'blue')
                    .on('mouseover', function(L, i) {
                        $.each(
                            $barchart.dataset(),
                            function (i, d) {
                                if (d.bar == L) {
                                    $barchart.showToolTip({ label : d.tooltip })
                                }
                            }
                        );

                    })
                    .on('mouseout', function(d) {
                        $barchart.hideToolTip();
                    })
            };

            this._rewireIds($elem, this);
            this.data('tableElem', $tableElem);
            this.data('barElem',   $barElem);
            this.data('container', $container);
            this.data('barchart', $barchart);

        },

    });

} );
<commit_msg>added rounding<commit_after>

define('kbaseExpressionSampleTable',
    [
        'jquery',
        'kbwidget',
        'kbaseAuthenticatedWidget',
        'kbaseTabs',
        'kbaseBarchart',
        'kbase-client-api',
        'kbaseTable',
        'jquery-dataTables',
    ], function( $, colorbrewer ) {

    'use strict';

    $.KBWidget({

	    name: "kbaseExpressionSampleTable",
	    parent : "kbaseAuthenticatedWidget",

        version: "1.0.0",
        options: {
            numBins : 10,
        },

        _accessors : [
            {name: 'dataset', setter: 'setDataset'},
            {name: 'barchartDataset', setter: 'setBarchartDataset'},
        ],

        setDataset : function setDataset(newDataset) {

            var rows = [];
            var barData = [];

            var min = Number.MAX_VALUE;
            var max = Number.MIN_VALUE;

            var exprKeys = Object.keys(newDataset.expression_levels).sort();

            $.each(
                exprKeys,
                function (i,k) {

                    var val = Math.round(newDataset.expression_levels[k] * 1000) / 1000;

                    rows.push( [k, val] );

                    if (val < min) {
                        min = val;
                    }
                    if (val > max) {
                        max = val;
                    }
                    barData.push(val);

                    /*var bin = Math.floor(newDataset.expression_levels[k]);

                    if (barData[bin] == undefined) {
                        barData[bin] = 0;
                    }
                    barData[bin]++;*/

                }
            );

            this.setBarchartDataset(barData);
            this.renderHistogram(this.options.numBins);

            var $dt = this.data('tableElem').dataTable({
                aoColumns : [
                    { title : 'Gene ID'},
                    { title : 'Feature Value'}
                ]
            });
            $dt.fnAddData(rows);

            this.data('loader').hide();
            this.data('containerElem').show();

        },

        renderHistogram : function renderHistogram(bins) {

            var barData = d3.layout.histogram().bins(bins)( this.barchartDataset() );

            var bars = [];
            var sigDigits = 1000;
            $.each(
                barData,
                function (i,bin) {
                    var range = Math.round(bin.x * sigDigits) / sigDigits + ' to ' + (Math.round((bin.x + bin.dx) * sigDigits) / sigDigits);

                    bars.push(
                        {
                            bar : range,
                            value : bin.y,
                            color : 'blue',
                            tooltip : bin.y + ' in range
' + range,
                        }
                    );
                }
            );

            this.data('barchart').setDataset(bars);

        },

        init : function init(options) {
            this._super(options);

            var $self = this;

            var ws = new Workspace(window.kbconfig.urls.workspace, {token : $self.authToken()});
            //var ws = new Workspace('https://ci.kbase.us/services/ws', {token : $self.authToken()});

            var ws_params = {
                workspace : this.options.workspace,
                wsid : this.options.wsid,
                name : this.options.output
            };

            ws.get_objects([ws_params]).then(function (d) {
                $self.setDataset(d[0].data);
            }).fail(function(d) {

                $self.$elem.empty();
                $self.$elem
                    .addClass('alert alert-danger')
                    .html("Could not load object : " + d.error.message);
            })

            this.appendUI(this.$elem);

            return this;
        },

        appendUI : function appendUI($elem) {

            var $me = this;

            var $tableElem = $.jqElem('table')
                .css('width', '95%')
                    .append(
                        $.jqElem('thead')
                            .append(
                                $.jqElem('tr')
                                    .append($.jqElem('th').append('Gene ID'))
                                    .append($.jqElem('th').append('Feature Value'))
                            )
                    )
            ;

            var $barElem = $.jqElem('div').css({width : 800, height : 500});

            var $barContainer = $.jqElem('div')
                .append(
                    $.jqElem('input')
                        .attr('type', 'range')
                        .attr('min', 0)
                        .attr('max', 100)
                        .attr('value', $me.options.numBins)
                        .attr('step', 1)
                        .css('width', '800px')
                        .on('input', function(e) {
                            $me.data('numBins').text($(this).val());
                        })
                        .on('change', function(e) {
                            $me.data('numBins').text($(this).val());
                            $me.renderHistogram(parseInt($(this).val()));
                        })
                )
                .append(
                    $.jqElem('span')
                        .attr('id', 'numBins')
                        .text($me.options.numBins)
                )
                .append(' bins
')
                .append($barElem)
            ;

            var $containerElem = $.jqElem('div').attr('id', 'containerElem').css('display', 'none');

            var $container = $containerElem.kbaseTabs(
                {
                    tabs : [
                        {
                            tab : 'Overview',
                            content : $tableElem
                        },
                        {
                            tab : 'Histogram',
                            content : $barContainer
                        }
                    ]
                }
            )

            $container.$elem.find('[data-tab=Histogram]').on('click', function(e) {
                $barchart.renderXAxis();
                setTimeout(function() {$me.renderHistogram($me.options.numBins) }, 300);
            });

            $elem
                .append( $containerElem )
                .append(
                    $.jqElem('div')
                        .attr('id', 'loader')
                        .append('
 Loading data...
 please wait...')
                        .append($.jqElem('br'))
                        .append(
                            $.jqElem('div')
                                .attr('align', 'center')
                                .append($.jqElem('i').addClass('fa fa-spinner').addClass('fa fa-spin fa fa-4x'))
                        )
                )
            ;

            var $barchart =
                $barElem.kbaseBarchart(
                    {
                        scaleAxes   : true,
                        xPadding : 60,
                        yPadding : 120,

                        xLabelRegion : 'yPadding',
                        yLabelRegion : 'xPadding',

                        xLabelOffset : 45,
                        yLabelOffset : -10,

                        yLabel : 'Number of Genes',
                        xLabel : 'Gene Expression Level log2(FPKM + 1)',
                        xAxisVerticalLabels : true

                    }
                )
            ;

            $barchart.superRenderXAxis = $barchart.renderXAxis;
            $barchart.renderXAxis = function() {
                $barchart.superRenderXAxis();

                $barchart.D3svg()
                    .selectAll('.xAxis .tick text')
                    .attr('fill', 'blue')
                    .on('mouseover', function(L, i) {
                        $.each(
                            $barchart.dataset(),
                            function (i, d) {
                                if (d.bar == L) {
                                    $barchart.showToolTip({ label : d.tooltip })
                                }
                            }
                        );

                    })
                    .on('mouseout', function(d) {
                        $barchart.hideToolTip();
                    })
            };

            this._rewireIds($elem, this);
            this.data('tableElem', $tableElem);
            this.data('barElem',   $barElem);
            this.data('container', $container);
            this.data('barchart', $barchart);

        },

    });

} );
<|endoftext|>


<commit_before>$( function()
{
  var MAX_ZOOM_LEVEL    = 8.0;
  var ZOOM_STEP_KEY     = 0.25;
  var ZOOM_STEP_WHEEL   = 0.125;

  // Check for the various File API support.
  if (!(window.File && window.FileReader && window.FileList && window.Blob))
  {
    alert('The File APIs are not fully supported in this browser.');
  }

  // Setup the dnd listeners.
  var dropZone = document.body;
  dropZone.addEventListener('dragover', handleDragOver, false);
  dropZone.addEventListener('drop', handleFileSelect, false);

  $('#file').on('change', function(e) {
    addFiles(e.target.files);
  });

  $(window).resize(updateDOM);
  $(window).keydown(function(e)
    {
      if (e.ctrlKey || e.altKey)
      {
        return true;
      }
      // '0' - '9'
      if (48 <= e.keyCode && e.keyCode <= 57 && !e.shiftKey)
      {
        currentImageIndex = e.keyCode - 48;
        updateDOM();
        return false;
      }
      // '+;' (59 or 187) / PageUp (33)
      if (e.keyCode == 59 || e.keyCode == 187 || (e.keyCode == 33 && !e.shiftKey))
      {
        viewZoom = Math.min(viewZoom + ZOOM_STEP_KEY, MAX_ZOOM_LEVEL);
        updateTransform();
        return false;
      }
      // '-' (173 or 189) / PageDown (34)
      if (e.keyCode == 173 || e.keyCode == 189 || (e.keyCode == 34 && !e.shiftKey))
      {
        viewZoom = Math.max(viewZoom - ZOOM_STEP_KEY, 0);
        updateTransform();
        return false;
      }
      // cursor key
      if (37 <= e.keyCode && e.keyCode <= 40)
      {
        var x = e.keyCode == 37 ? -1 : e.keyCode == 39 ? 1 : 0;
        var y = e.keyCode == 38 ? -1 : e.keyCode == 40 ? 1 : 0;
        viewOffset.x += x * 0.4 / scale;
        viewOffset.y += y * 0.4 / scale;
        viewOffset.x = Math.min(1, Math.max(0, viewOffset.x));
        viewOffset.y = Math.min(1, Math.max(0, viewOffset.y));
        updateTransform();
        return false;
      }
      // ESC (27)
      if (e.keyCode == 27 && !e.shiftKey)
      {
        currentImageIndex = 0;
        viewZoom = 0;
        viewOffset.x = 0.5;
        viewOffset.y = 0.5;
        updateDOM();
        return false;
      }
      // 'f' (70)
      if (e.keyCode == 70 && !e.shiftKey)
      {
        toggleFullscreen();
        return false;
      }
      //alert(e.keyCode);
    });

  $(window).on("wheel", function(e)
  {
    var event = e.originalEvent;
    if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey)
    {
        return true;
    }
    if (event.deltaY < 0)
    {
        viewZoom = Math.min(viewZoom + ZOOM_STEP_WHEEL, MAX_ZOOM_LEVEL);
        updateTransform();
        return false;
    }
    else if (event.deltaY > 0)
    {
        viewZoom = Math.max(viewZoom - ZOOM_STEP_WHEEL, 0);
        updateTransform();
        return false;
    }
  });

  updateDOM();
});

  var images = [];
  var log = [];
  var currentImageIndex = 0;
  var viewZoom = 0;
  var scale = 1.0;
  var viewOffset = { x : 0.5, y : 0.5 };
  var dragLastPoint = null;

  function escapeHtml(str)
  {
    str = str.replace(/&/g, '&');
    str = str.replace(/</g, '<');
    str = str.replace(/>/g, '>');
    str = str.replace(/"/g, '"');
    str = str.replace(/'/g, ''');
    return str;
  }

  function updateDOM()
  {
    var view = document.getElementById('view');
    var vw = $(view).width();
    var vh = $(view).height();

    var isSingleView =
            currentImageIndex != 0 &&
            currentImageIndex <= images.length;
    var numColumns =
            isSingleView ? 1 :
            2 < images.length ? images.length : 2;

    dragLastPoint = null;

    view.innerHTML = '';
    var html = [];
    var htmlCount = 0;
    for (var i = 0, img; img = images[i]; i++)
    {
        if (isSingleView && i + 1 != currentImageIndex)
        {
            continue;
        }
        var isLetterBox = vw / numColumns * img.height < vh * img.width;
        if (isLetterBox)
        {
            $(img.element).css( { width : '100%', height : 'auto' });
        }
        else
        {
            $(img.element).css( { width : 'auto', height : '100%' });
        }
        $(view).append(
            $('
').append(
                img.element,
                $('').addClass('imageName').
                    text(''+(i + 1) + ': ' + img.name)
            )
        );
        $(img.element).off('mousedown');
        $(img.element).on('mousedown', function(e)
        {
          if (e.which == 1)
          {
            dragLastPoint = { x : e.clientX, y : e.clientY };
            return false;
          }
        });
        $(img.element).off('mousemove');
        $(img.element).on('mousemove', {
            baseW : (isLetterBox ? vw / numColumns : vh * img.width / img.height),
            baseH : (isLetterBox ? vw / numColumns * img.height / img.width : vh) }, function(e)
        {
          if (dragLastPoint && e.buttons != 1)
          {
            dragLastPoint = null;
          }
          if (dragLastPoint)
          {
            var dx = e.clientX - dragLastPoint.x;
            var dy = e.clientY - dragLastPoint.y;
            dragLastPoint = { x : e.clientX, y : e.clientY };
            var x = dx / e.data.baseW;
            var y = dy / e.data.baseH;
            if (1.0 < scale)
            {
              viewOffset.x -= (x / scale) / (1.0 - 1.0 / scale);
              viewOffset.y -= (y / scale) / (1.0 - 1.0 / scale);
            }
            viewOffset.x = Math.min(1, Math.max(0, viewOffset.x));
            viewOffset.y = Math.min(1, Math.max(0, viewOffset.y));
            updateTransform();
            return false;
          }
        });
        $(img.element).off('mouseup');
        $(img.element).on('mouseup', function(e)
        {
          dragLastPoint = null;
        });
        htmlCount += 1;
    }
    while (htmlCount < numColumns)
    {
        $(view).append(
            $('
').append(
                $('
').addClass('dropHere').
                    text("Drop image files here").
                    click(function() {
                      $('#file').click();
                    })
            )
        );
        htmlCount += 1;
    }

    $('#view > div').addClass('imageBox').css(
        {
            width       : ''+(100/numColumns)+'%',
        });

    updateTransform();
  }

  function updateTransform() {

    var scalePercent = Math.round(Math.pow(2.0, viewZoom) * 100);
    scale = scalePercent / 100;
    var offsetX = (50 - 100 * viewOffset.x) * (1.0 - 1.0 / scale);
    var offsetY = (50 - 100 * viewOffset.y) * (1.0 - 1.0 / scale);
    $('#view .imageBox img').css(
                {
                    left        : '50%',
                    top         : '50%',
                    transform   : 'translate(-50%, -50%) ' +
                                  'scale(' + scale + ') ' +
                                  'translate(' + offsetX + '%, ' + offsetY + '%)',
                });
  }

  function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    addFiles(evt.dataTransfer.files);
  }
  function addFiles(files)
  {
    // files is a FileList of File objects.
    for (var i = 0, f; f = files[i]; i++)
    {
      if (!f.type || !(f.type == 'image/png' || f.type == 'image/jpeg'))
      {
        log.push('Error: ', escapeHtml(f.name), ' is not an image
');
      }
      else
      {
        var reader = new FileReader();
        reader.onload = (function(theFile)
        {
            return function(e)
            {
                var img = new Image;
                $(img).on('load', function()
                {
                    images.push(
                        {
                            element : img,
                            width   : img.width,
                            height  : img.height,
                            name    : theFile.name,
                        });
                    updateDOM();
                });
                img.src = e.target.result;
            };
        })(f);
        reader.readAsDataURL(f);
      }
    }
    currentImageIndex = 0;
    updateDOM();
    document.getElementById('log').innerHTML = log.join('');
  }

  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy';
  }

  function toggleFullscreen()
  {
    var fullscreen = document.fullscreenElement ||
                document.webkitFullscreenElement ||
                document.mozFullScreenElement;
    if (!fullscreen)
    {
      var view = document.getElementById('view');
      if (view.webkitRequestFullscreen) {
        view.webkitRequestFullscreen();
      } else if (view.mozRequestFullScreen) {
        view.mozRequestFullScreen();
      } else {
        view.requestFullscreen();
      }
    }
    else
    {
      if (document.webkitExitFullscreen) {
        document.webkitExitFullscreen();
      } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
      } else {
        document.exitFullscreen();
      }
    }
  }
<commit_msg>Improve wheel zoom behavior using proportional action<commit_after>$( function()
{
  var MAX_ZOOM_LEVEL    = 6.0;
  var ZOOM_STEP_KEY     = 0.25;
  var ZOOM_STEP_WHEEL   = 0.0625;

  // Check for the various File API support.
  if (!(window.File && window.FileReader && window.FileList && window.Blob))
  {
    alert('The File APIs are not fully supported in this browser.');
  }

  // Setup the dnd listeners.
  var dropZone = document.body;
  dropZone.addEventListener('dragover', handleDragOver, false);
  dropZone.addEventListener('drop', handleFileSelect, false);

  $('#file').on('change', function(e) {
    addFiles(e.target.files);
  });

  $(window).resize(updateDOM);
  $(window).keydown(function(e)
    {
      if (e.ctrlKey || e.altKey)
      {
        return true;
      }
      // '0' - '9'
      if (48 <= e.keyCode && e.keyCode <= 57 && !e.shiftKey)
      {
        currentImageIndex = e.keyCode - 48;
        updateDOM();
        return false;
      }
      // '+;' (59 or 187) / PageUp (33)
      if (e.keyCode == 59 || e.keyCode == 187 || (e.keyCode == 33 && !e.shiftKey))
      {
        viewZoom = Math.min(viewZoom + ZOOM_STEP_KEY, MAX_ZOOM_LEVEL);
        updateTransform();
        return false;
      }
      // '-' (173 or 189) / PageDown (34)
      if (e.keyCode == 173 || e.keyCode == 189 || (e.keyCode == 34 && !e.shiftKey))
      {
        viewZoom = Math.max(viewZoom - ZOOM_STEP_KEY, 0);
        updateTransform();
        return false;
      }
      // cursor key
      if (37 <= e.keyCode && e.keyCode <= 40)
      {
        var x = e.keyCode == 37 ? -1 : e.keyCode == 39 ? 1 : 0;
        var y = e.keyCode == 38 ? -1 : e.keyCode == 40 ? 1 : 0;
        viewOffset.x += x * 0.4 / scale;
        viewOffset.y += y * 0.4 / scale;
        viewOffset.x = Math.min(1, Math.max(0, viewOffset.x));
        viewOffset.y = Math.min(1, Math.max(0, viewOffset.y));
        updateTransform();
        return false;
      }
      // ESC (27)
      if (e.keyCode == 27 && !e.shiftKey)
      {
        currentImageIndex = 0;
        viewZoom = 0;
        viewOffset.x = 0.5;
        viewOffset.y = 0.5;
        updateDOM();
        return false;
      }
      // 'f' (70)
      if (e.keyCode == 70 && !e.shiftKey)
      {
        toggleFullscreen();
        return false;
      }
      //alert(e.keyCode);
    });

  $(window).on("wheel", function(e)
  {
    var event = e.originalEvent;
    if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey)
    {
        return true;
    }
    var deltaScale = event.deltaMode == 0 ? /* PIXEL */ 0.1 : /* LINE */ 1.0;
    var steps = Math.max(-3, Math.min(3, event.deltaY * deltaScale));
    if (steps != 0)
    {
        viewZoom = Math.max(0, Math.min(MAX_ZOOM_LEVEL, viewZoom - steps * ZOOM_STEP_WHEEL));
        updateTransform();
        return false;
    }
  });

  updateDOM();
});

  var images = [];
  var log = [];
  var currentImageIndex = 0;
  var viewZoom = 0;
  var scale = 1.0;
  var viewOffset = { x : 0.5, y : 0.5 };
  var dragLastPoint = null;

  function escapeHtml(str)
  {
    str = str.replace(/&/g, '&');
    str = str.replace(/</g, '<');
    str = str.replace(/>/g, '>');
    str = str.replace(/"/g, '"');
    str = str.replace(/'/g, ''');
    return str;
  }

  function updateDOM()
  {
    var view = document.getElementById('view');
    var vw = $(view).width();
    var vh = $(view).height();

    var isSingleView =
            currentImageIndex != 0 &&
            currentImageIndex <= images.length;
    var numColumns =
            isSingleView ? 1 :
            2 < images.length ? images.length : 2;

    dragLastPoint = null;

    view.innerHTML = '';
    var html = [];
    var htmlCount = 0;
    for (var i = 0, img; img = images[i]; i++)
    {
        if (isSingleView && i + 1 != currentImageIndex)
        {
            continue;
        }
        var isLetterBox = vw / numColumns * img.height < vh * img.width;
        if (isLetterBox)
        {
            $(img.element).css( { width : '100%', height : 'auto' });
        }
        else
        {
            $(img.element).css( { width : 'auto', height : '100%' });
        }
        $(view).append(
            $('
').append(
                img.element,
                $('').addClass('imageName').
                    text(''+(i + 1) + ': ' + img.name)
            )
        );
        $(img.element).off('mousedown');
        $(img.element).on('mousedown', function(e)
        {
          if (e.which == 1)
          {
            dragLastPoint = { x : e.clientX, y : e.clientY };
            return false;
          }
        });
        $(img.element).off('mousemove');
        $(img.element).on('mousemove', {
            baseW : (isLetterBox ? vw / numColumns : vh * img.width / img.height),
            baseH : (isLetterBox ? vw / numColumns * img.height / img.width : vh) }, function(e)
        {
          if (dragLastPoint && e.buttons != 1)
          {
            dragLastPoint = null;
          }
          if (dragLastPoint)
          {
            var dx = e.clientX - dragLastPoint.x;
            var dy = e.clientY - dragLastPoint.y;
            dragLastPoint = { x : e.clientX, y : e.clientY };
            var x = dx / e.data.baseW;
            var y = dy / e.data.baseH;
            if (1.0 < scale)
            {
              viewOffset.x -= (x / scale) / (1.0 - 1.0 / scale);
              viewOffset.y -= (y / scale) / (1.0 - 1.0 / scale);
            }
            viewOffset.x = Math.min(1, Math.max(0, viewOffset.x));
            viewOffset.y = Math.min(1, Math.max(0, viewOffset.y));
            updateTransform();
            return false;
          }
        });
        $(img.element).off('mouseup');
        $(img.element).on('mouseup', function(e)
        {
          dragLastPoint = null;
        });
        htmlCount += 1;
    }
    while (htmlCount < numColumns)
    {
        $(view).append(
            $('
').append(
                $('
').addClass('dropHere').
                    text("Drop image files here").
                    click(function() {
                      $('#file').click();
                    })
            )
        );
        htmlCount += 1;
    }

    $('#view > div').addClass('imageBox').css(
        {
            width       : ''+(100/numColumns)+'%',
        });

    updateTransform();
  }

  function updateTransform() {

    var scalePercent = Math.round(Math.pow(2.0, viewZoom) * 100);
    scale = scalePercent / 100;
    var offsetX = (50 - 100 * viewOffset.x) * (1.0 - 1.0 / scale);
    var offsetY = (50 - 100 * viewOffset.y) * (1.0 - 1.0 / scale);
    $('#view .imageBox img').css(
                {
                    left        : '50%',
                    top         : '50%',
                    transform   : 'translate(-50%, -50%) ' +
                                  'scale(' + scale + ') ' +
                                  'translate(' + offsetX + '%, ' + offsetY + '%)',
                });
  }

  function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    addFiles(evt.dataTransfer.files);
  }
  function addFiles(files)
  {
    // files is a FileList of File objects.
    for (var i = 0, f; f = files[i]; i++)
    {
      if (!f.type || !(f.type == 'image/png' || f.type == 'image/jpeg'))
      {
        log.push('Error: ', escapeHtml(f.name), ' is not an image
');
      }
      else
      {
        var reader = new FileReader();
        reader.onload = (function(theFile)
        {
            return function(e)
            {
                var img = new Image;
                $(img).on('load', function()
                {
                    images.push(
                        {
                            element : img,
                            width   : img.width,
                            height  : img.height,
                            name    : theFile.name,
                        });
                    updateDOM();
                });
                img.src = e.target.result;
            };
        })(f);
        reader.readAsDataURL(f);
      }
    }
    currentImageIndex = 0;
    updateDOM();
    document.getElementById('log').innerHTML = log.join('');
  }

  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy';
  }

  function toggleFullscreen()
  {
    var fullscreen = document.fullscreenElement ||
                document.webkitFullscreenElement ||
                document.mozFullScreenElement;
    if (!fullscreen)
    {
      var view = document.getElementById('view');
      if (view.webkitRequestFullscreen) {
        view.webkitRequestFullscreen();
      } else if (view.mozRequestFullScreen) {
        view.mozRequestFullScreen();
      } else {
        view.requestFullscreen();
      }
    }
    else
    {
      if (document.webkitExitFullscreen) {
        document.webkitExitFullscreen();
      } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
      } else {
        document.exitFullscreen();
      }
    }
  }
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { connect, batch } from 'react-redux';

import { MODAL_KEYS, getModalTitle } from '../../utilities';
import { usePageReducer } from '../../hooks';
import { getItemLayout } from '../../pages/dataTableUtilities';

import { GenericChoiceList } from '../modalChildren/GenericChoiceList';
import { PageInfo, DataTablePageView, PageButton } from '..';
import { DataTable, DataTableHeaderRow, DataTableRow } from '../DataTable';

import globalStyles from '../../globalStyles';

import { UIDatabase } from '../../database';
import { ModalContainer } from './ModalContainer';
import { buttonStrings } from '../../localization';
import { ROUTES } from '../../navigation/constants';

import { selectUsingPayments } from '../../selectors/modules';
import { AutocompleteSelector } from '../modalChildren';

/**
 * Renders a stateful modal with a stocktake item and it's batches loaded
 * for editing.
 *
 * State:
 * Uses a reducer to manage state with `backingData` being a realm results
 * of items to display. `data` is a plain JS array of realm objects. data is
 * hydrated from the `backingData` for displaying in the interface.
 * i.e: When filtering, data is populated from filtered items of `backingData`.
 *
 * dataState is a simple map of objects corresponding to a row being displayed,
 * holding the state of a given row. Each object has the shape :
 * { isSelected, isFocused, isDisabled },
 *
 * @prop {Object} stocktakeItem The realm transaction object for this invoice.
 * @prop {Object} page the current routeName for this modal.
 *
 */
export const StocktakeBatchModalComponent = ({ stocktakeItem, page, dispatch: reduxDispatch }) => {
  const initialState = {
    page,
    pageObject: stocktakeItem,
  };

  const [state, dispatch, instantDebouncedDispatch] = usePageReducer(initialState);

  const {
    pageObject,
    data,
    dataState,
    sortKey,
    isAscending,
    modalKey,
    modalValue,
    keyExtractor,
    PageActions,
    columns,
    getPageInfoColumns,
    suppliers,
  } = state;

  const { stocktake = {} } = stocktakeItem;
  const { isFinalised = false } = stocktake;
  const { difference = 0 } = modalValue || {};

  const reasonsSelection =
    difference > 0
      ? UIDatabase.objects('PositiveAdjustmentReason')
      : UIDatabase.objects('NegativeAdjustmentReason');

  const onEditSupplier = value => dispatch(PageActions.editBatchSupplier(value, modalValue));
  const onSelectSupplier = rowKey =>
    dispatch(PageActions.openModal('selectItemBatchSupplier', rowKey));
  const onCloseModal = () => dispatch(PageActions.closeModal());
  const onApplyReason = ({ item }) => dispatch(PageActions.applyReason(item));
  const onAddBatch = () => dispatch(PageActions.addStocktakeBatch());
  const onEditBatch = (value, rowKey, columnKey) =>
    dispatch(PageActions.editStocktakeBatchName(value, rowKey, columnKey));
  const onEditReason = rowKey =>
    batch(() => {
      dispatch(PageActions.openModal(MODAL_KEYS.STOCKTAKE_REASON, rowKey));
      reduxDispatch(PageActions.refreshRow(stocktakeItem.id, ROUTES.STOCKTAKE_EDITOR));
    });
  const onEditCountedQuantity = (newValue, rowKey, columnKey) =>
    batch(() => {
      dispatch(PageActions.editStocktakeBatchCountedQuantity(newValue, rowKey, columnKey));
      reduxDispatch(PageActions.refreshRow(stocktakeItem.id, ROUTES.STOCKTAKE_EDITOR));
    });
  const onEditDate = (date, rowKey, columnKey) =>
    dispatch(PageActions.editStocktakeBatchExpiryDate(date, rowKey, columnKey));
  const onEditSellPrice = (newValue, rowKey) =>
    dispatch(PageActions.editSellPrice(newValue, rowKey));

  const toggles = useCallback(getPageInfoColumns(pageObject, dispatch, PageActions), []);

  const getCallback = useCallback(colKey => {
    switch (colKey) {
      case 'countedTotalQuantity':
        return onEditCountedQuantity;
      case 'batch':
        return onEditBatch;
      case 'expiryDate':
        return onEditDate;
      case 'reasonTitle':
        return onEditReason;
      case 'sellPriceString':
        return onEditSellPrice;
      case 'otherPartyName':
        return onSelectSupplier;
      default:
        return null;
    }
  }, []);

  const renderRow = useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = keyExtractor(item);
      return (
        <DataTableRow
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          columns={columns}
          getCallback={getCallback}
          rowIndex={index}
          isFinalised={isFinalised}
        />
      );
    },
    [data, dataState]
  );

  const renderHeader = useCallback(
    () => (
      <DataTableHeaderRow
        columns={columns}
        dispatch={instantDebouncedDispatch}
        sortAction={PageActions.sortData}
        isAscending={isAscending}
        sortKey={sortKey}
      />
    ),
    [sortKey, isAscending]
  );

  const {
    pageTopSectionContainer,
    pageTopLeftSectionContainer,
    pageTopRightSectionContainer,
  } = globalStyles;
  return (
    <DataTablePageView>
      <View style={pageTopSectionContainer}>
        <View style={pageTopLeftSectionContainer}>
          <PageInfo columns={toggles} />
        </View>
        <View style={pageTopRightSectionContainer}>
          <PageButton
            text={buttonStrings.add_batch}
            onPress={onAddBatch}
            isDisabled={isFinalised}
          />
        </View>
      </View>
      <DataTable
        data={data}
        extraData={dataState}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={keyExtractor}
        getItemLayout={getItemLayout}
        columns={columns}
        windowSize={1}
        initialNumToRender={0}
      />
      <ModalContainer
        fullScreen={modalKey === MODAL_KEYS.ENFORCE_STOCKTAKE_REASON}
        isVisible={!!modalKey}
        onClose={onCloseModal}
        title={getModalTitle(modalKey)}
      >
        {modalKey === MODAL_KEYS.SELECT_ITEM_BATCH_SUPPLIER ? (
          <AutocompleteSelector
            options={suppliers}
            onSelect={onEditSupplier}
            sortKeyString="name"
            queryString="name contains[c] $0"
            renderRightText={({ code }) => code}
          />
        ) : (
          <GenericChoiceList
            data={reasonsSelection}
            highlightValue={(modalValue && modalValue.reasonTitle) || ''}
            keyToDisplay="title"
            onPress={onApplyReason}
          />
        )}
      </ModalContainer>
    </DataTablePageView>
  );
};

StocktakeBatchModalComponent.propTypes = {
  stocktakeItem: PropTypes.object.isRequired,
  page: PropTypes.string.isRequired,
  dispatch: PropTypes.func.isRequired,
};

const mapStateToProps = state => {
  const usingPaymentsModule = selectUsingPayments(state);
  const usingReasons =
    UIDatabase.objects('NegativeAdjustmentReason').length > 0 &&
    UIDatabase.objects('PositiveAdjustmentReason').length > 0;

  if (usingReasons) {
    if (usingPaymentsModule) return { page: 'stocktakeBatchEditModalWithReasonsAndPrices' };
    return { page: 'stocktakeBatchEditModalWithReasons' };
  }

  if (usingPaymentsModule) return { page: 'stocktakeBatchEditModalWithPrices' };
  return { page: 'stocktakeBatchEditModal' };
};

export const StocktakeBatchModal = connect(mapStateToProps)(StocktakeBatchModalComponent);
<commit_msg>Add hide snapshot pref logic to stocktake batch modal<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { connect, batch } from 'react-redux';

import { MODAL_KEYS, getModalTitle } from '../../utilities';
import { usePageReducer } from '../../hooks';
import { getItemLayout } from '../../pages/dataTableUtilities';
import { COLUMN_KEYS } from '../../pages/dataTableUtilities/constants';

import { GenericChoiceList } from '../modalChildren/GenericChoiceList';
import { PageInfo, DataTablePageView, PageButton } from '..';
import { DataTable, DataTableHeaderRow, DataTableRow } from '../DataTable';

import globalStyles from '../../globalStyles';

import { UIDatabase } from '../../database';
import { ModalContainer } from './ModalContainer';
import { buttonStrings } from '../../localization';
import { ROUTES } from '../../navigation/constants';

import { selectUsingPayments, selectHideSnapshotColumn } from '../../selectors/modules';
import { AutocompleteSelector } from '../modalChildren';

/**
 * Renders a stateful modal with a stocktake item and it's batches loaded
 * for editing.
 *
 * State:
 * Uses a reducer to manage state with `backingData` being a realm results
 * of items to display. `data` is a plain JS array of realm objects. data is
 * hydrated from the `backingData` for displaying in the interface.
 * i.e: When filtering, data is populated from filtered items of `backingData`.
 *
 * dataState is a simple map of objects corresponding to a row being displayed,
 * holding the state of a given row. Each object has the shape :
 * { isSelected, isFocused, isDisabled },
 *
 * @prop {Object} stocktakeItem The realm transsingHaction object for this invoice.
 * @prop {Object} page the current routeName for this modal.
 *
 */
export const StocktakeBatchModalComponent = ({
  stocktakeItem,
  usingPayments,
  usingReasons,
  hideSnapshotColumn,
  dispatch: reduxDispatch,
}) => {
  const initialState = {
    page: 'stocktakeBatchEditModal',
    pageObject: stocktakeItem,
  };

  if (usingReasons) {
    initialState.page += 'WithReasons';
    if (usingPayments) initialState.page += 'AndPrices';
  } else if (usingPayments) initialState.page += 'WithPrices';

  const [state, dispatch, instantDebouncedDispatch] = usePageReducer(initialState);

  const {
    pageObject,
    data,
    dataState,
    sortKey,
    isAscending,
    modalKey,
    modalValue,
    keyExtractor,
    PageActions,
    columns: pageColumns,
    getPageInfoColumns,
    suppliers,
  } = state;

  const columns = hideSnapshotColumn
    ? pageColumns.filter(({ key }) => key !== COLUMN_KEYS.SNAPSHOT_TOTAL_QUANTITY)
    : pageColumns;

  const { stocktake = {} } = stocktakeItem;
  const { isFinalised = false } = stocktake;
  const { difference = 0 } = modalValue || {};

  const reasonsSelection =
    difference > 0
      ? UIDatabase.objects('PositiveAdjustmentReason')
      : UIDatabase.objects('NegativeAdjustmentReason');

  const onEditSupplier = value => dispatch(PageActions.editBatchSupplier(value, modalValue));
  const onSelectSupplier = rowKey =>
    dispatch(PageActions.openModal('selectItemBatchSupplier', rowKey));
  const onCloseModal = () => dispatch(PageActions.closeModal());
  const onApplyReason = ({ item }) => dispatch(PageActions.applyReason(item));
  const onAddBatch = () => dispatch(PageActions.addStocktakeBatch());
  const onEditBatch = (value, rowKey, columnKey) =>
    dispatch(PageActions.editStocktakeBatchName(value, rowKey, columnKey));
  const onEditReason = rowKey =>
    batch(() => {
      dispatch(PageActions.openModal(MODAL_KEYS.STOCKTAKE_REASON, rowKey));
      reduxDispatch(PageActions.refreshRow(stocktakeItem.id, ROUTES.STOCKTAKE_EDITOR));
    });
  const onEditCountedQuantity = (newValue, rowKey, columnKey) =>
    batch(() => {
      dispatch(PageActions.editStocktakeBatchCountedQuantity(newValue, rowKey, columnKey));
      reduxDispatch(PageActions.refreshRow(stocktakeItem.id, ROUTES.STOCKTAKE_EDITOR));
    });
  const onEditDate = (date, rowKey, columnKey) =>
    dispatch(PageActions.editStocktakeBatchExpiryDate(date, rowKey, columnKey));
  const onEditSellPrice = (newValue, rowKey) =>
    dispatch(PageActions.editSellPrice(newValue, rowKey));

  const toggles = useCallback(getPageInfoColumns(pageObject, dispatch, PageActions), []);

  const getCallback = useCallback(colKey => {
    switch (colKey) {
      case 'countedTotalQuantity':
        return onEditCountedQuantity;
      case 'batch':
        return onEditBatch;
      case 'expiryDate':
        return onEditDate;
      case 'reasonTitle':
        return onEditReason;
      case 'sellPriceString':
        return onEditSellPrice;
      case 'otherPartyName':
        return onSelectSupplier;
      default:
        return null;
    }
  }, []);

  const renderRow = useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = keyExtractor(item);
      return (
        <DataTableRow
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          columns={columns}
          getCallback={getCallback}
          rowIndex={index}
          isFinalised={isFinalised}
        />
      );
    },
    [data, dataState]
  );

  const renderHeader = useCallback(
    () => (
      <DataTableHeaderRow
        columns={columns}
        dispatch={instantDebouncedDispatch}
        sortAction={PageActions.sortData}
        isAscending={isAscending}
        sortKey={sortKey}
      />
    ),
    [sortKey, isAscending]
  );

  const {
    pageTopSectionContainer,
    pageTopLeftSectionContainer,
    pageTopRightSectionContainer,
  } = globalStyles;
  return (
    <DataTablePageView>
      <View style={pageTopSectionContainer}>
        <View style={pageTopLeftSectionContainer}>
          <PageInfo columns={toggles} />
        </View>
        <View style={pageTopRightSectionContainer}>
          <PageButton
            text={buttonStrings.add_batch}
            onPress={onAddBatch}
            isDisabled={isFinalised}
          />
        </View>
      </View>
      <DataTable
        data={data}
        extraData={dataState}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={keyExtractor}
        getItemLayout={getItemLayout}
        columns={columns}
        windowSize={1}
        initialNumToRender={0}
      />
      <ModalContainer
        fullScreen={modalKey === MODAL_KEYS.ENFORCE_STOCKTAKE_REASON}
        isVisible={!!modalKey}
        onClose={onCloseModal}
        title={getModalTitle(modalKey)}
      >
        {modalKey === MODAL_KEYS.SELECT_ITEM_BATCH_SUPPLIER ? (
          <AutocompleteSelector
            options={suppliers}
            onSelect={onEditSupplier}
            sortKeyString="name"
            queryString="name contains[c] $0"
            renderRightText={({ code }) => code}
          />
        ) : (
          <GenericChoiceList
            data={reasonsSelection}
            highlightValue={(modalValue && modalValue.reasonTitle) || ''}
            keyToDisplay="title"
            onPress={onApplyReason}
          />
        )}
      </ModalContainer>
    </DataTablePageView>
  );
};

StocktakeBatchModalComponent.propTypes = {
  stocktakeItem: PropTypes.object.isRequired,
  usingPayments: PropTypes.bool.isRequired,
  usingReasons: PropTypes.bool.isRequired,
  hideSnapshotColumn: PropTypes.bool.isRequired,
  dispatch: PropTypes.func.isRequired,
};

const mapStateToProps = state => {
  const usingPayments = selectUsingPayments(state);
  const usingReasons =
    UIDatabase.objects('NegativeAdjustmentReason').length > 0 &&
    UIDatabase.objects('PositiveAdjustmentReason').length > 0;
  const hideSnapshotColumn = selectHideSnapshotColumn(state);
  return { usingPayments, usingReasons, hideSnapshotColumn };
};

export const StocktakeBatchModal = connect(mapStateToProps)(StocktakeBatchModalComponent);
<|endoftext|>


<commit_before>
/* =========================================================================
 * Svelto - Widgets - Selectable - Actions
 * =========================================================================
 * Copyright (c) 2015-2016 Fabio Spampinato
 * Licensed under MIT (https://github.com/svelto/svelto/blob/master/LICENSE)
 * =========================================================================
 * @before widgets/remote/action/action.js
 * @before widgets/remote/modal/modal.js
 * @require ../selectable.js
 * @require widgets/targeter/targeter.js
 * @require widgets/toast/toast.js
 * ========================================================================= */

//TODO: Cross-browser test

(function ( $, _, Svelto, Widgets, Factory, Pointer ) {

  'use strict';

  /* CONFIG */

  let config = {
    name: 'selectableActions',
    plugin: true,
    selector: '.selectable-actions',
    options: {
      ajax: {}, // Default values
      actions: {
        action ( ajax ) {
          new Widgets.RemoteAction ( { ajax: ajax } ).request ();
        },
        modal ( ajax ) {
          new Widgets.RemoteModal ( { ajax: ajax } ).request ();
        },
        page ( ajax ) {
          window.location.href = ajax.url;
        }
      },
      defaultType: 'page',
      widget: Widgets.Selectable,
      characters: {
        separator: ','
      },
      messages: {
        no_selected: 'No entries selected'
      },
      placeholders: {
        id: '%ID%'
      },
      attributes: {
        href: 'href' // In order to better support `a` elements (the data value has higher priority)
      },
      datas: {
        id: 'id',
        type: 'type',
        url: 'url',
        data: 'data',
        method: 'method'
      },
      selectors: {
        action: '.action',
        id: false // Selects the element containing the id (from it's `tr` element), for instance it could be `> td:first-child`. If falsy, `datas.id` will be used
      },
      callbacks: {
        action: _.noop
      }
    }
  };

  /* SELECTABLE ACTIONS */

  class SelectableActions extends Widgets.Targeter {

    /* SPECIAL */

    _variables () {

      super._variables ();

      this.$wrapper = this.$element;
      this.$actions = this.$wrapper.find ( this.options.selectors.action );

    }

    _events () {

      super._events ();

      this.___action ();

    }

    /* ACTION */

    ___action () {

      this._on ( this.$actions, Pointer.tap, this.__action );

    }

    __action ( event ) {

      let ids = this._getIds ();

      if ( !ids.length ) return $.toast ( this.options.messages.no_selected );

      let $action = $(event.target),
          type = $action.data ( this.options.datas.type ) || this.options.defaultType,
          action = this.options.actions[type];

      if ( !action ) return;

      let url = $action.data ( this.options.datas.url ) || $action.attr ( this.options.attributes.href ) || this.options.ajax.url,
          data = $action.data ( this.options.datas.data ) || this.options.ajax.data,
          method = $action.data ( this.options.datas.method ) || this.options.ajax.method;

      url = url.replace ( this.options.placeholders.id, ids.join ( this.options.characters.separator ) );
      data = _.extend ( data || {}, { ids: ids } );

      let ajax = _.extend ( {}, this.options.ajax, {
        url: url,
        data: data,
        method: method
      });

      action ( ajax );

      this._trigger ( 'action', event, { type: type, ajax: ajax } );

    }

    /* IDs */

    _getIds () {

      let $rows = this._targetInstance.get (),
          ids = $rows.get ().map ( row => this.options.selectors.id ? $(row).find ( this.options.selectors.id ).text () : $(row).data ( this.options.datas.id ) );

      return _.compact ( ids );

    }

  }

  /* FACTORY */

  Factory.init ( SelectableActions, config, Widgets );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory, Svelto.Pointer ));
<commit_msg>Renamed SelectableActions.config.options.defaultType to SelectableActions.config.options.defaultAction<commit_after>
/* =========================================================================
 * Svelto - Widgets - Selectable - Actions
 * =========================================================================
 * Copyright (c) 2015-2016 Fabio Spampinato
 * Licensed under MIT (https://github.com/svelto/svelto/blob/master/LICENSE)
 * =========================================================================
 * @before widgets/remote/action/action.js
 * @before widgets/remote/modal/modal.js
 * @require ../selectable.js
 * @require widgets/targeter/targeter.js
 * @require widgets/toast/toast.js
 * ========================================================================= */

//TODO: Cross-browser test

(function ( $, _, Svelto, Widgets, Factory, Pointer ) {

  'use strict';

  /* CONFIG */

  let config = {
    name: 'selectableActions',
    plugin: true,
    selector: '.selectable-actions',
    options: {
      ajax: {}, // Default values
      actions: {
        action ( ajax ) {
          new Widgets.RemoteAction ( { ajax: ajax } ).request ();
        },
        modal ( ajax ) {
          new Widgets.RemoteModal ( { ajax: ajax } ).request ();
        },
        page ( ajax ) {
          window.location.href = ajax.url;
        }
      },
      defaultAction: 'page',
      widget: Widgets.Selectable,
      characters: {
        separator: ','
      },
      messages: {
        no_selected: 'No entries selected'
      },
      placeholders: {
        id: '%ID%'
      },
      attributes: {
        href: 'href' // In order to better support `a` elements (the data value has higher priority)
      },
      datas: {
        id: 'id',
        type: 'type',
        url: 'url',
        data: 'data',
        method: 'method'
      },
      selectors: {
        action: '.action',
        id: false // Selects the element containing the id (from it's `tr` element), for instance it could be `> td:first-child`. If falsy, `datas.id` will be used
      },
      callbacks: {
        action: _.noop
      }
    }
  };

  /* SELECTABLE ACTIONS */

  class SelectableActions extends Widgets.Targeter {

    /* SPECIAL */

    _variables () {

      super._variables ();

      this.$wrapper = this.$element;
      this.$actions = this.$wrapper.find ( this.options.selectors.action );

    }

    _events () {

      super._events ();

      this.___action ();

    }

    /* ACTION */

    ___action () {

      this._on ( this.$actions, Pointer.tap, this.__action );

    }

    __action ( event ) {

      let ids = this._getIds ();

      if ( !ids.length ) return $.toast ( this.options.messages.no_selected );

      let $action = $(event.target),
          type = $action.data ( this.options.datas.type ) || this.options.defaultAction,
          action = this.options.actions[type];

      if ( !action ) return;

      let url = $action.data ( this.options.datas.url ) || $action.attr ( this.options.attributes.href ) || this.options.ajax.url,
          data = $action.data ( this.options.datas.data ) || this.options.ajax.data,
          method = $action.data ( this.options.datas.method ) || this.options.ajax.method;

      url = url.replace ( this.options.placeholders.id, ids.join ( this.options.characters.separator ) );
      data = _.extend ( data || {}, { ids: ids } );

      let ajax = _.extend ( {}, this.options.ajax, {
        url: url,
        data: data,
        method: method
      });

      action ( ajax );

      this._trigger ( 'action', event, { type: type, ajax: ajax } );

    }

    /* IDs */

    _getIds () {

      let $rows = this._targetInstance.get (),
          ids = $rows.get ().map ( row => this.options.selectors.id ? $(row).find ( this.options.selectors.id ).text () : $(row).data ( this.options.datas.id ) );

      return _.compact ( ids );

    }

  }

  /* FACTORY */

  Factory.init ( SelectableActions, config, Widgets );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory, Svelto.Pointer ));
<|endoftext|>


<commit_before>/*
How to create a battle scene.
Scene(createBattleScene([
  [`avenger`, [0, 0, 0]],
  [`child`, [1, 0, -1]],
  [`protector`, [-1, 1, 0]],
  [`persecutor`, [-1, 0, 1]],
],5));
*/

import { add } from 'pura/vector/tuple';
import { camera, } from 'camera';
import { uiElements } from 'ui';
// import Song from 'audio';
// import battleMusic from 'songs/summer';
// import finalBossMusic from 'songs/winter';
import Menu from 'menu';
import StatusBar from 'battle/statusBar';
import BattleMap from 'battle/map';
import { initializeMap, turnOrder, moveCharacter, getGridHexFromVector2d } from 'battle/grid';
import Move from 'battle/actions/Move';
import {
  Attack, Defend, Magic, Item,
} from 'battle/actions/PlayerAbilities';
import Swarmer from 'battle/actions/Swarmer';
import Vamp from 'battle/actions/Vamp';
import Skeli from 'battle/actions/Skeli';
import Resentment from 'battle/actions/Resentment';
import Doubt from 'battle/actions/Doubt';
import Deceit from 'battle/actions/Deceit';
import state from 'state';
import { handlStatuses } from 'battle/actions/utility';

const cameraOffset = [0, -150];

function* generateGetCharacter(turnOrder) {
  let i = 0;
  while(true) {
    const char = turnOrder[i % (turnOrder.length)];
    ++i;
    const [, health] = char;
    if(health > 0) {
      yield char;
    }
  }
}

let harmTurns = 0;

export default function createBattleScene(characters, mapSize) {
  initializeMap(characters, mapSize);

  let turn = null;
  let selectedAction = null;
  const action = {
    // Player Controlled Character Movement
    move: [`Move`, () => selectedAction = Move],
    attack: [`Avenge`, () => selectedAction = Attack],
    defend: [`Protect`, () => selectedAction = Defend],
    magic: [`Forget`, () => selectedAction = Magic],
    item: [`Use`, () => selectedAction = Item],
    swarmer: Swarmer,
    vamp: Vamp,
    skeli: Skeli,
    resentment: Resentment,
    doubt: Doubt,
    deceit: Deceit,
    *harm() {
      yield* [Doubt, Resentment, Deceit][++harmTurns % 9 / 3 | 0];
    }
  };

  const getCharacter = generateGetCharacter(turnOrder);
  function* Turn() {
    // 1) Determine who's turn it is.
    const { value: character } = getCharacter.next();
    const [data, health, position] = character;
    state.target = getGridHexFromVector2d(position);
    console.log(`------------------------------`);
    console.log(`${data.name} : ${health}`);
    // 2) Center Camera on that person.
    const panCamera = moveCharacter(camera, add(cameraOffset, position), 2000);
    console.log(`Paning Camera from ${camera} to ${add(cameraOffset, position)}`);
    while(!panCamera()) yield;
    // 3) Look up actions
    selectedAction = null;
    if(data.type) {
      let actions;
      switch(data.name) {
        case `Persecutor`:
          actions = [
            action.move,
            action.item,
          ];
          break;
        default:
          actions = Object.keys(data.abilities).map(name => action[name]);
        // 4) Add actions to the menu
      }
      const menuUIIndex = uiElements.push(Menu(actions));
      // 5) Wait for player to select an action.
      while(!selectedAction) yield;
      uiElements.splice(menuUIIndex - 1, 1);
    } else {
      selectedAction = action[data.name.toLowerCase()];
    }
    // 6) Execute selected action
    yield* selectedAction(character);
    handlStatuses(character);
    // 7) Check to see if battle is over
    const isVictory = false;//!!turnOrder.find(([data, health]) => !data.alignment && health > 0);
    const isDefeat = false;//!isVictory && !!turnOrder.find(([data, health]) => data.alignment && health > 0);
    console.log(`------------------------------`);
    return isVictory ? 1 :
      isDefeat ? 2 :
        0;
  }

  // const bgMusic = new Song(battleMusic || finalBossMusic);

  return {
    init: () => {
      // bgMusic.play(bgMusic);
      camera[2] = 340;
      uiElements.push(BattleMap, StatusBar);
      turn = Turn();
      state.logic = () => {
        // done: is turn over.
        // value: battle status, 0: ongoing, 1: victory, 2: defeat;
        const { value, done } = turn.next();
        if(done && value === 0) {
          // 8) Increment turn counter
          // 9A) Create New turn
          turn = Turn();
        } else if(done && value === 1) {
          // 9B) VICTORY
        } else if(done && value === 2) {
          // 9C) DEFEAT
        }
      };
    },
    dismiss: () => {
      // bgMusic.stop();
    },
  };
}
<commit_msg>Remove console.log<commit_after>/*
How to create a battle scene.
Scene(createBattleScene([
  [`avenger`, [0, 0, 0]],
  [`child`, [1, 0, -1]],
  [`protector`, [-1, 1, 0]],
  [`persecutor`, [-1, 0, 1]],
],5));
*/

import { add } from 'pura/vector/tuple';
import { camera, } from 'camera';
import { uiElements } from 'ui';
// import Song from 'audio';
// import battleMusic from 'songs/summer';
// import finalBossMusic from 'songs/winter';
import Menu from 'menu';
import StatusBar from 'battle/statusBar';
import BattleMap from 'battle/map';
import { initializeMap, turnOrder, moveCharacter, getGridHexFromVector2d } from 'battle/grid';
import Move from 'battle/actions/Move';
import {
  Attack, Defend, Magic, Item,
} from 'battle/actions/PlayerAbilities';
import Swarmer from 'battle/actions/Swarmer';
import Vamp from 'battle/actions/Vamp';
import Skeli from 'battle/actions/Skeli';
import Resentment from 'battle/actions/Resentment';
import Doubt from 'battle/actions/Doubt';
import Deceit from 'battle/actions/Deceit';
import state from 'state';
import { handlStatuses } from 'battle/actions/utility';

const cameraOffset = [0, -150];

function* generateGetCharacter(turnOrder) {
  let i = 0;
  while(true) {
    const char = turnOrder[i % (turnOrder.length)];
    ++i;
    const [, health] = char;
    if(health > 0) {
      yield char;
    }
  }
}

let harmTurns = 0;

export default function createBattleScene(characters, mapSize) {
  initializeMap(characters, mapSize);

  let turn = null;
  let selectedAction = null;
  const action = {
    // Player Controlled Character Movement
    move: [`Move`, () => selectedAction = Move],
    attack: [`Avenge`, () => selectedAction = Attack],
    defend: [`Protect`, () => selectedAction = Defend],
    magic: [`Forget`, () => selectedAction = Magic],
    item: [`Use`, () => selectedAction = Item],
    swarmer: Swarmer,
    vamp: Vamp,
    skeli: Skeli,
    resentment: Resentment,
    doubt: Doubt,
    deceit: Deceit,
    *harm() {
      yield* [Doubt, Resentment, Deceit][++harmTurns % 9 / 3 | 0];
    }
  };

  const getCharacter = generateGetCharacter(turnOrder);
  function* Turn() {
    // 1) Determine who's turn it is.
    const { value: character } = getCharacter.next();
    const [data, health, position] = character;
    state.target = getGridHexFromVector2d(position);
    console.log(`------------------------------`);
    console.log(`${data.name} : ${health}`);
    // 2) Center Camera on that person.
    const panCamera = moveCharacter(camera, add(cameraOffset, position), 2000);
    while(!panCamera()) yield;
    // 3) Look up actions
    selectedAction = null;
    if(data.type) {
      let actions;
      switch(data.name) {
        case `Persecutor`:
          actions = [
            action.move,
            action.item,
          ];
          break;
        default:
          actions = Object.keys(data.abilities).map(name => action[name]);
        // 4) Add actions to the menu
      }
      const menuUIIndex = uiElements.push(Menu(actions));
      // 5) Wait for player to select an action.
      while(!selectedAction) yield;
      uiElements.splice(menuUIIndex - 1, 1);
    } else {
      selectedAction = action[data.name.toLowerCase()];
    }
    // 6) Execute selected action
    yield* selectedAction(character);
    handlStatuses(character);
    // 7) Check to see if battle is over
    const isVictory = false;//!!turnOrder.find(([data, health]) => !data.alignment && health > 0);
    const isDefeat = false;//!isVictory && !!turnOrder.find(([data, health]) => data.alignment && health > 0);
    console.log(`------------------------------`);
    return isVictory ? 1 :
      isDefeat ? 2 :
        0;
  }

  // const bgMusic = new Song(battleMusic || finalBossMusic);

  return {
    init: () => {
      // bgMusic.play(bgMusic);
      camera[2] = 340;
      uiElements.push(BattleMap, StatusBar);
      turn = Turn();
      state.logic = () => {
        // done: is turn over.
        // value: battle status, 0: ongoing, 1: victory, 2: defeat;
        const { value, done } = turn.next();
        if(done && value === 0) {
          // 8) Increment turn counter
          // 9A) Create New turn
          turn = Turn();
        } else if(done && value === 1) {
          // 9B) VICTORY
        } else if(done && value === 2) {
          // 9C) DEFEAT
        }
      };
    },
    dismiss: () => {
      // bgMusic.stop();
    },
  };
}
<|endoftext|>


<commit_before>var fs = require('fs');
var _ = require('underscore');
var sys = require('sys')
var exec = require('child_process').exec;

if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(pattern) {
    pattern = new RegExp("^" + pattern);
    return !!pattern.exec(this);
  }
}

function chunkLines(content, categorizer) {
  var block = {lines: []};
  var blocks = [];

  for (i in lines = content.split("\n")) {
    var line = lines[i];
    var kind = categorizer(line);

    if (block.kind != kind) {
      if (block.writing) {
        blocks.push(block);
      }

      block = {lines: []};
      block.writing = true;
      block.kind = kind;
    }

    block.lines.push(line);
  }

  if(block.writing) {
    blocks.push(block);
  }

  blocks.eachBlock = function(process, callback) {
    var mutex = 0;
    var memo = {}
    _(this).each(function(block) {
      process(block.lines.join("\n"), block.kind, memo, function() {
        console.log("came back!")
        mutex -= 1;
        if (mutex == 0) {
          callback();
        }
      });
      mutex += 1;
    })
  }

  return blocks;
}

function writeFile(filename, content, callback) {
  exec("mkdir -p build", function() {
    fs.writeFile('build/' + filename, content, callback);
  });
}

function handleBlock(block, kind, memo, callback) {
  if (matches = block.match(/`([^`]+)`/)) {
    memo.filename = matches[1]
  }
  if ((kind == 'code') && memo.filename) {
    block = block.replace(/(^|\n)    /g, "$1");
    writeFile(memo.filename, block, callback);
    memo.filename = null;
  }
}

fs.readFile('README.md', 'utf-8', function(err, content) {
  byCodeOrComment = function(line) {
    return line.startsWith('    ') ? 'code' : 'comment';
  }

  chunkLines(content, byCodeOrComment).eachBlock(handleBlock, function() {
    console.log("and we're done!");
  });
});
<commit_msg>Make compile.js all synchronous<commit_after>var fs = require('fs');
var _ = require('underscore');
var sys = require('sys')
var exec = require('child_process').exec;

if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(pattern) {
    pattern = new RegExp("^" + pattern);
    return !!pattern.exec(this);
  }
}

function chunkLines(content, categorizer) {
  var block = {lines: []};
  var blocks = [];

  for (i in lines = content.split("\n")) {
    var line = lines[i];
    var kind = categorizer(line);

    if (block.kind != kind) {
      if (block.writing) {
        blocks.push(block);
      }

      block = {lines: []};
      block.writing = true;
      block.kind = kind;
    }

    block.lines.push(line);
  }

  if(block.writing) {
    blocks.push(block);
  }

  blocks.eachBlock = function(process) {
    var memo = {}
    _(this).each(function(block, i) {
      process(block.lines.join("\n"), block.kind, memo);
    })
  }

  return blocks;
}

function writeFile(filename, content, callback) {
  exec("mkdir -p build", function() {
    fs.writeFile('build/' + filename, content, callback);
  });
}

function handleBlock(block, kind, memo) {
  if (matches = block.match(/`([^`]+)`/)) {
    memo.filename = matches[1]
  }

  if ((kind == 'code') && memo.filename) {
    block = block.replace(/(^|\n)    /g, "$1");
    writeFile(memo.filename, block);
  }
}

fs.readFile('README.md', 'utf-8', function(err, content) {
  byCodeOrComment = function(line) {
    return line.startsWith('    ') ? 'code' : 'comment';
  }

  chunkLines(content, byCodeOrComment).eachBlock(handleBlock)
  console.log("Look in build/ for your stuff!");
});
<|endoftext|>


<commit_before>/**
 * 接管sass的@import内嵌，让fis-sass支持sass语法内嵌。
 *
 * 目前fis-sass无法改动libsass(第三方库)内部的代码以至于无法支持sass格式的文件`@import`内嵌。
 *
 * 所以这里接管内嵌，绕过fis-sass处理`@import`内嵌过程。
 *
 * 目前fis-sass的是sass语法支持，主要是用了[sass2scss](https://github.com/mgreter/sass2scss)库，
 * 目前只有程序的最外成入口处调用了这个功能，libsass的内联file读取没有经过这一过程，所以这个功能是不完美支持sass语法的。
 */
var map = (function() {
    return {
        reg: /\/\*embed:(.*?)\*\//ig,

        wrap: function( value ) {
            return '/*embed:' + value + '*/';
        }
    };
})();

var sass = require('fis-sass');
var stack = {};
var cache;

function fixLineBreak( content ) {
    return content.replace(/\r\n|\r|\n/g, '\n');
}

// 匹配scss内嵌代码：@import 'xxx';
// @import 'xxx', 'xxx';
function _process( content ) {
    var reg = /((?:\/\/.*?\n)|(?:\/\*[\s\S]*?\*\/))|(?:@import\s([\s\S]*?)(?:\n|$)(?!\s+\S))/ig;
    // var reg = /@import[\s\S]*?;/ig;
    var rUrl = /^url/i;
    var rEnd = /;$/;

    content = fixLineBreak( content );

    return content.replace( reg, function( all, comments, value ) {
        var files;

        value = value ? value.trim().replace( rEnd, '' ) : '';

        // If it's comments or If the @import has any media queries.
        if ( comments || /('|").*?\1\s+[^'"]+$/.exec(value) ) {
            return all;
        }

        files = value.split(/\s*,?\s+/m).map(function( value ) {
            var quote = '',
                match;

            value = value.trim();
            match = /^('|")(.*)\1$/.exec( value );

            if ( match ) {
                value = match[ 2 ];
                quote = match[ 1 ];
            }

            // If the file’s extension is .css.
            if ( /\.css$/i.exec( value ) ||

                // If the filename begins with http://.
                /^http/i.exec( value ) ||

                // If the filename is a url().
                /^url/i.exec( value ) ) {
                return '@import ' + quote + value + quote + ';';
            }

            return map.wrap( value );
        });

        return files.join('\n');
    });
}

// 查找文件。
function lookup( name, ext, paths ) {
    var files = [],
        filename = name,
        basename = filename,
        dirname = '',
        mapping,
        i, j, len, len2, path, info;

    files.push( filename );

    if ( /^(.*(?:\/|\\))([^\/\\]*?)$/.exec( filename ) ) {
        dirname = RegExp.$1;
        basename = RegExp.$2;
    }

    // 自动加后缀。
    if ( !/\.\w+$/.exec( basename ) ) {
        filename = dirname + basename + ext;
        files.push( filename );

        if ( !/^_/.exec( basename ) ) {
            filename = dirname + '_' + basename + ext;
            files.push( filename );
        }

        mapping = {
            '.sass': '.scss',
            '.scss': '.sass'
        };

        if ( mapping[ ext ] ) {
            filename = dirname + basename + mapping[ ext ];
            files.push( filename );

            if ( !/^_/.exec( basename ) ) {
                filename = dirname + '_' + basename + mapping[ ext ];
                files.push( filename );
            }
        }
    }

    len = files.length;
    len2 = paths.length;

    for ( i = 0; i < len; i++ ) {
        name = files[ i ];

        for ( j = 0; j < len2; j++ ) {
            info = fis.uri( name, paths[ j ] );

            if( info.file && info.file.isFile() ) {
                return info.file;
            }
        }
    }

    // console.log( paths, files );
}

function isSassSyntax( content, file ) {
    return file.ext === '.sass' && !~content.indexOf('{');
}

function unique( arr ) {
    return arr.filter(function( item, index, arr ) {
        return arr.indexOf( item ) === index;
    });
}

var compile = module.exports = function( content, file, opts ) {

    if ( isSassSyntax( content, file ) ) {
        content = sass.sass2scss( content );
    }

    cache = {};
    opts.data = before( content, file, unique( opts.include_paths ) );
    content = sass.renderSync( opts );
    content = after( content, file, opts.include_paths );

    return content;
};

var before = compile.before = function( content, currentFile, paths) {

    var ext = currentFile.ext;

    paths = unique( paths );

    return _process( content ).replace( map.reg, function( all, value ) {
        var file = lookup( value, ext, paths ),
            content;

        if ( file ) {

            // 已经在堆里面了
            if ( stack[ file.realpath ] ) {
                fis.log.error( 'can\'t embed file ' + file.basename + '.' );
            }

            // 如果已经引入过了，就不再引入。
            // 类似与include_once.
            if ( cache[ file.realpath ] ) {
                return '';
            }

            currentFile.cache.addDeps(file.realpath);
            stack[ file.realpath ] = true;
            cache[ file.realpath ] = true;

            content = file.getContent();
            if ( isSassSyntax( content, file ) ) {
                content = sass.sass2scss( content );
            }

            content = before( content, file, [ file.dirname ].concat( paths ) );

            delete stack[ file.realpath ];

            return content;

        } else {
            fis.log.error( value + ' not found!' );
            return '';
        }
    });
};

var after = compile.after = function( content, currentFile, paths ) {
    return content;
};<commit_msg>修复内嵌资源可能未经过fis.compile 而导致fis.cache为undefined 报错的bug<commit_after>/**
 * 接管sass的@import内嵌，让fis-sass支持sass语法内嵌。
 *
 * 目前fis-sass无法改动libsass(第三方库)内部的代码以至于无法支持sass格式的文件`@import`内嵌。
 *
 * 所以这里接管内嵌，绕过fis-sass处理`@import`内嵌过程。
 *
 * 目前fis-sass的是sass语法支持，主要是用了[sass2scss](https://github.com/mgreter/sass2scss)库，
 * 目前只有程序的最外成入口处调用了这个功能，libsass的内联file读取没有经过这一过程，所以这个功能是不完美支持sass语法的。
 */
var map = (function() {
    return {
        reg: /\/\*embed:(.*?)\*\//ig,

        wrap: function( value ) {
            return '/*embed:' + value + '*/';
        }
    };
})();

var sass = require('fis-sass');
var stack = {};
var cache;

function fixLineBreak( content ) {
    return content.replace(/\r\n|\r|\n/g, '\n');
}

// 匹配scss内嵌代码：@import 'xxx';
// @import 'xxx', 'xxx';
function _process( content ) {
    var reg = /((?:\/\/.*?\n)|(?:\/\*[\s\S]*?\*\/))|(?:@import\s([\s\S]*?)(?:\n|$)(?!\s+\S))/ig;
    // var reg = /@import[\s\S]*?;/ig;
    var rUrl = /^url/i;
    var rEnd = /;$/;

    content = fixLineBreak( content );

    return content.replace( reg, function( all, comments, value ) {
        var files;

        value = value ? value.trim().replace( rEnd, '' ) : '';

        // If it's comments or If the @import has any media queries.
        if ( comments || /('|").*?\1\s+[^'"]+$/.exec(value) ) {
            return all;
        }

        files = value.split(/\s*,?\s+/m).map(function( value ) {
            var quote = '',
                match;

            value = value.trim();
            match = /^('|")(.*)\1$/.exec( value );

            if ( match ) {
                value = match[ 2 ];
                quote = match[ 1 ];
            }

            // If the file’s extension is .css.
            if ( /\.css$/i.exec( value ) ||

                // If the filename begins with http://.
                /^http/i.exec( value ) ||

                // If the filename is a url().
                /^url/i.exec( value ) ) {
                return '@import ' + quote + value + quote + ';';
            }

            return map.wrap( value );
        });

        return files.join('\n');
    });
}

// 查找文件。
function lookup( name, ext, paths ) {
    var files = [],
        filename = name,
        basename = filename,
        dirname = '',
        mapping,
        i, j, len, len2, path, info;

    files.push( filename );

    if ( /^(.*(?:\/|\\))([^\/\\]*?)$/.exec( filename ) ) {
        dirname = RegExp.$1;
        basename = RegExp.$2;
    }

    // 自动加后缀。
    if ( !/\.\w+$/.exec( basename ) ) {
        filename = dirname + basename + ext;
        files.push( filename );

        if ( !/^_/.exec( basename ) ) {
            filename = dirname + '_' + basename + ext;
            files.push( filename );
        }

        mapping = {
            '.sass': '.scss',
            '.scss': '.sass'
        };

        if ( mapping[ ext ] ) {
            filename = dirname + basename + mapping[ ext ];
            files.push( filename );

            if ( !/^_/.exec( basename ) ) {
                filename = dirname + '_' + basename + mapping[ ext ];
                files.push( filename );
            }
        }
    }

    len = files.length;
    len2 = paths.length;

    for ( i = 0; i < len; i++ ) {
        name = files[ i ];

        for ( j = 0; j < len2; j++ ) {
            info = fis.uri( name, paths[ j ] );

            if( info.file && info.file.isFile() ) {
                return info.file;
            }
        }
    }

    // console.log( paths, files );
}

function isSassSyntax( content, file ) {
    return file.ext === '.sass' && !~content.indexOf('{');
}

function unique( arr ) {
    return arr.filter(function( item, index, arr ) {
        return arr.indexOf( item ) === index;
    });
}

var compile = module.exports = function( content, file, opts ) {

    if ( isSassSyntax( content, file ) ) {
        content = sass.sass2scss( content );
    }

    cache = {};
    opts.data = before( content, file, unique( opts.include_paths ) );
    content = sass.renderSync( opts );
    content = after( content, file, opts.include_paths );

    return content;
};

var before = compile.before = function( content, currentFile, paths) {

    var ext = currentFile.ext;

    paths = unique( paths );

    return _process( content ).replace( map.reg, function( all, value ) {
        var file = lookup( value, ext, paths ),
            content;

        if ( file ) {

            // 已经在堆里面了
            if ( stack[ file.realpath ] ) {
                fis.log.error( 'can\'t embed file ' + file.basename + '.' );
            }

            // 如果已经引入过了，就不再引入。
            // 类似与include_once.
            if ( cache[ file.realpath ] ) {
                return '';
            }

            // 有可能当前文件并没有经过 fis 的compile，以至于没有 cahce 对象。
            if (currentFile.cache) {
                currentFile.cache.addDeps(file.realpath);
            }

            stack[ file.realpath ] = true;
            cache[ file.realpath ] = true;

            content = file.getContent();
            if ( isSassSyntax( content, file ) ) {
                content = sass.sass2scss( content );
            }

            content = before( content, file, [ file.dirname ].concat( paths ) );

            delete stack[ file.realpath ];

            return content;

        } else {
            fis.log.error( value + ' not found!' );
            return '';
        }
    });
};

var after = compile.after = function( content, currentFile, paths ) {
    return content;
};<|endoftext|>


<commit_before>/**
 * 接管sass的@import内嵌，让fis-sass支持sass语法内嵌。
 *
 * 目前fis-sass无法改动libsass(第三方库)内部的代码以至于无法支持sass格式的文件`@import`内嵌。
 *
 * 所以这里接管内嵌，让fis-sass不处理`@import`内嵌代码。
 */
var map = (function() {
    return {
        reg: /\/\*embed:(.*?)\*\//ig,

        wrap: function( value ) {
            return '/*embed:' + value + '*/';
        }
    }
})();

// 匹配sass内嵌代码：@import 'xxx'
function _process( content ) {
    var reg = /@import\s*('|")?(.+?)\1(?:;)?$/img;
    var rUrl = /^url/i;

    return content.replace( reg, function( all, quote, value ) {

        // 不处理@import url(xxx);
        if ( value && rUrl.exec( value ) ) {
            return all;
        }

        return map.wrap( value );
    });
}

// 查找文件。
function lookup( name, ext, paths ) {
    var files = [],
        filename = name,
        basename = filename,
        dirname = '',
        mapping,
        i, j, len, len2, path, info;

    files.push( filename );

    if ( /^(.*(?:\/|\\))([^\/\\]*?)$/.exec( filename ) ) {
        dirname = RegExp.$1;
        basename = RegExp.$2;
    }

    // 自动加后缀。
    if ( !/\.\w+$/.exec( basename ) ) {
        filename = dirname + basename + ext;
        files.push( filename );

        if ( !/^_/.exec( basename ) ) {
            filename = dirname + '_' + basename + ext;
            files.push( filename );
        }

        mapping = {
            '.sass': '.scss',
            '.scss': '.sass'
        };

        if ( mapping[ ext ] ) {
            filename = dirname + basename + mapping[ ext ];
            files.push( filename );

            if ( !/^_/.exec( basename ) ) {
                filename = dirname + '_' + basename + mapping[ ext ];
                files.push( filename );
            }
        }
    }

    len = files.length;
    len2 = paths.length;

    for ( i = 0; i < len; i++ ) {
        name = files[ i ];

        for ( j = 0; j < len2; j++ ) {
            info = fis.uri( name, paths[ j ] );

            if( info.file && info.file.isFile() ) {
                return info.file;
            }
        }
    }
}

var compile = exports.before = function( content, ext, paths ) {
    return _process( content ).replace( map.reg, function( all, value ) {
        var file = lookup( value, ext, paths );

        if ( file ) {
            // @todo 祈祷，不要循环内嵌吧。
            return compile( file.getContent(), file.ext, [ file.dirname ].concat( paths ) );
        } else {
            fis.log.error( value + ' not found!' );
            return '';
        }
    });
};

exports.after = function( content, ext, paths ) {
    return content;
};<commit_msg>update doc<commit_after>/**
 * 接管sass的@import内嵌，让fis-sass支持sass语法内嵌。
 *
 * 目前fis-sass无法改动libsass(第三方库)内部的代码以至于无法支持sass格式的文件`@import`内嵌。
 *
 * 所以这里接管内嵌，绕过fis-sass处理`@import`内嵌过程。
 *
 * 目前fis-sass的是sass语法支持，主要是用了[sass2scss](https://github.com/mgreter/sass2scss)库，
 * 目前只有程序的最外成入口处调用了这个功能，libsass的内联file读取没有经过这一过程，所以这个功能是不完美支持sass语法的。
 */
var map = (function() {
    return {
        reg: /\/\*embed:(.*?)\*\//ig,

        wrap: function( value ) {
            return '/*embed:' + value + '*/';
        }
    }
})();

// 匹配sass内嵌代码：@import 'xxx'
function _process( content ) {
    var reg = /@import\s*('|")?(.+?)\1(?:;)?$/img;
    var rUrl = /^url/i;

    return content.replace( reg, function( all, quote, value ) {

        // 不处理@import url(xxx);
        if ( value && rUrl.exec( value ) ) {
            return all;
        }

        return map.wrap( value );
    });
}

// 查找文件。
function lookup( name, ext, paths ) {
    var files = [],
        filename = name,
        basename = filename,
        dirname = '',
        mapping,
        i, j, len, len2, path, info;

    files.push( filename );

    if ( /^(.*(?:\/|\\))([^\/\\]*?)$/.exec( filename ) ) {
        dirname = RegExp.$1;
        basename = RegExp.$2;
    }

    // 自动加后缀。
    if ( !/\.\w+$/.exec( basename ) ) {
        filename = dirname + basename + ext;
        files.push( filename );

        if ( !/^_/.exec( basename ) ) {
            filename = dirname + '_' + basename + ext;
            files.push( filename );
        }

        mapping = {
            '.sass': '.scss',
            '.scss': '.sass'
        };

        if ( mapping[ ext ] ) {
            filename = dirname + basename + mapping[ ext ];
            files.push( filename );

            if ( !/^_/.exec( basename ) ) {
                filename = dirname + '_' + basename + mapping[ ext ];
                files.push( filename );
            }
        }
    }

    len = files.length;
    len2 = paths.length;

    for ( i = 0; i < len; i++ ) {
        name = files[ i ];

        for ( j = 0; j < len2; j++ ) {
            info = fis.uri( name, paths[ j ] );

            if( info.file && info.file.isFile() ) {
                return info.file;
            }
        }
    }
}

var compile = exports.before = function( content, ext, paths ) {
    return _process( content ).replace( map.reg, function( all, value ) {
        var file = lookup( value, ext, paths );

        if ( file ) {
            // @todo 祈祷，不要循环内嵌吧。
            return compile( file.getContent(), file.ext, [ file.dirname ].concat( paths ) );
        } else {
            fis.log.error( value + ' not found!' );
            return '';
        }
    });
};

exports.after = function( content, ext, paths ) {
    return content;
};<|endoftext|>


<commit_before>// This uses the Google Charts API along with AudioChart.
//
// You can check out the finished ["hello, world" HTML file](http://matatk.agrip.org.uk/audiochart/examples/hello/) to experience the actual chart (this is just the code that makes it tick).
//
// Note that you can also use AudioChart with JSON and HTML tables (there are [examples](http://matatk.agrip.org.uk/audiochart/examples/examples-gallery.html) to demonstrate this).
//
// More details on working with Google Charts can be found in the [Google Charts documentation](https://developers.google.com/chart/).
'use strict'
google.load('visualization', '1.0', {'packages':['corechart']})
google.setOnLoadCallback(drawChart)

function drawChart() {
	// ## Google Charts Bits

	// Create a new data table
	var data = new google.visualization.DataTable()

	// Populate the table
	data.addColumn('string', 'Top Secret Evil Project')
	data.addColumn('number', 'Watermelons')
	data.addRows([
		['Alpha',    293],
		['Beta',     329],
		['Gamma',    261],
		['Delta',    130],
		['Epsilon',  196],
		['Zeta',     196],
	])

	// Google Chart options
	var chartOptions = {
		'title': 'Evil Project Efficacy',
		'curveType': 'function'
	}

	// Initialise (but do not yet actually draw) the chart
	var chart = new google.visualization.LineChart(
		document.getElementById('chart'))

	// Trigger an initial draw
	resizeChart()

	// Handle resizing when the viewport size changes ([thanks asgallant of StackOverflow](http://stackoverflow.com/a/23594901))
	function resizeChart() {
		chart.draw(data, chartOptions)
	}

	if (document.addEventListener) {
		window.addEventListener('resize', resizeChart)
	} else if (document.attachEvent) {
		window.attachEvent('onresize', resizeChart)
	} else {
		window.resize = resizeChart
	}

	// ## AudioChart Stuff

	// Create a new `AudioChart` object
	var ac = new AudioChart({
		'type': 'google',      // (see the docs)
		'data': data,          // the GoogleDataTable
		'chart': chart,        // the Google Chart object
		'duration': 5000,      // milliseconds
		'frequencyLow': 200,   // Hz
		'frequencyHigh': 600,  // Hz
		'chartContainer': document.getElementById('chart')  // HTMLElement e.g. HTMLDivElement
	})

	// Attach the `.playPause()` function to the `click` event handler of
	// the `<button>` element on the HTML page.
	document.getElementById('play').onclick = function() {
		ac.playPause()
	}
}
<commit_msg>Fix broken link to examples from tutorial comments<commit_after>// This uses the Google Charts API along with AudioChart.
//
// You can check out the finished ["hello, world" HTML file](http://matatk.agrip.org.uk/audiochart/examples/hello/) to experience the actual chart (this is just the code that makes it tick).
//
// Note that you can also use AudioChart with JSON and HTML tables (there are [examples](http://matatk.agrip.org.uk/audiochart/examples/) to demonstrate this).
//
// More details on working with Google Charts can be found in the [Google Charts documentation](https://developers.google.com/chart/).
'use strict'
google.load('visualization', '1.0', {'packages':['corechart']})
google.setOnLoadCallback(drawChart)

function drawChart() {
	// ## Google Charts Bits

	// Create a new data table
	var data = new google.visualization.DataTable()

	// Populate the table
	data.addColumn('string', 'Top Secret Evil Project')
	data.addColumn('number', 'Watermelons')
	data.addRows([
		['Alpha',    293],
		['Beta',     329],
		['Gamma',    261],
		['Delta',    130],
		['Epsilon',  196],
		['Zeta',     196],
	])

	// Google Chart options
	var chartOptions = {
		'title': 'Evil Project Efficacy',
		'curveType': 'function'
	}

	// Initialise (but do not yet actually draw) the chart
	var chart = new google.visualization.LineChart(
		document.getElementById('chart'))

	// Trigger an initial draw
	resizeChart()

	// Handle resizing when the viewport size changes ([thanks asgallant of StackOverflow](http://stackoverflow.com/a/23594901))
	function resizeChart() {
		chart.draw(data, chartOptions)
	}

	if (document.addEventListener) {
		window.addEventListener('resize', resizeChart)
	} else if (document.attachEvent) {
		window.attachEvent('onresize', resizeChart)
	} else {
		window.resize = resizeChart
	}

	// ## AudioChart Stuff

	// Create a new `AudioChart` object
	var ac = new AudioChart({
		'type': 'google',      // (see the docs)
		'data': data,          // the GoogleDataTable
		'chart': chart,        // the Google Chart object
		'duration': 5000,      // milliseconds
		'frequencyLow': 200,   // Hz
		'frequencyHigh': 600,  // Hz
		'chartContainer': document.getElementById('chart')  // HTMLElement e.g. HTMLDivElement
	})

	// Attach the `.playPause()` function to the `click` event handler of
	// the `<button>` element on the HTML page.
	document.getElementById('play').onclick = function() {
		ac.playPause()
	}
}
<|endoftext|>