<commit_before>
var animationSpeed = 1000;
var currentHover = null;
var zoomTimeout = null;
var url = 'http://localhost:9998/app/';
var ratio = 0;
var minHeight = 300;
var yZoom = 1;

$('document').ready(function() {

    //Update splashscreen
    splashScreen(function() {
        $('body').bind('mousewheel DOMMouseScroll', function(e) {
            if ($(currentHover).hasClass('minimap')) {
                e.preventDefault();
                zoom(e.originalEvent.wheelDelta);
            }
        });

        //Add the slider
        var slider = $('#minimap').find('.slider');
        $(slider)
            .draggable({
                containment: "parent",
                stop: function() {
                    clearTimeout(zoomTimeout);
                    zoomTimeout = setTimeout(function() { updatezoomWindow() }, 500);
                }
            });

        $('.genome').hover(function() {
            currentHover = this;
        }, function() {
            currentHover = null;
        });

        initialize();
    });
});

function splashScreen(callback)
{
    $('#splashscreen')
        .fadeIn(animationSpeed)
        .animate({'left':'0', 'top':'0', 'width':'300px'}, animationSpeed, function() {
            var width = ($( window ).width() - 2) + 'px';
            $('#header').show().animate({'left':'0', 'top':'0', 'width':width}, animationSpeed, function() {
                $('#container').fadeIn();
                callback();
            })
        });
}

function pxToInt(css) {
    return parseFloat(css.replace('px', ''));
}

var drawZoom = function(nodes) {
    draw(nodes, $('#zoomWindow canvas')[0], function(x, y) {
        var slider = $('#minimap .slider');
        var sliderRatio = $('#zoomWindow').width() / slider.width();
        return {
            x: (x / ratio - pxToInt(slider.css('left'))) * sliderRatio,
            y: (y / ratio - pxToInt(slider.css('top'))) * sliderRatio
        }
    });
};

var drawGenome = function(nodes) {
    draw(nodes, $('#minimap canvas')[0], function(x, y) {
        return {
            x: x / ratio,
            y: y / ratio
        }
    });
};

function draw(data, c, translate) {
    var points = data.cList;
    var ctx = c.getContext("2d");
    ctx.clearRect(0, 0, c.width, c.height);
    $.each(points, function(id, value) {
        ctx.beginPath();
        var coor = translate(value.x, value.y);

        ctx.arc(coor.x, coor.y, value.weight / 10, 0, 2 * Math.PI);
        ctx.stroke();
        $.each(value.edges, function(key, edge) {
            if (edge.targetX != -1 && edge.targetY != -1) {
                ctx.beginPath();
                ctx.moveTo(coor.x, coor.y);
                var targetCoor = translate(edge.targetX, edge.targetY);
                ctx.lineTo(targetCoor.x, targetCoor.y);
                ctx.lineWidth = edge.weight;
                ctx.stroke();
            }
        });
    });
}

function zoom(direction) {
    var slider = $('.slider', currentHover);
    var minimap = $('#minimap');
    var zoomWindow = $('#zoomWindow');
    var currentWidth = slider.width();
    var currentHeight = slider.height();
    var maxWidth = minimap.width();
    var maxHeight = minimap.height();
    var zoomRatio = zoomWindow.height() / zoomWindow.width();
    var newWidth = 0;
    if (direction > 0) {
        newWidth = Math.max(Math.min(currentWidth * 0.95, currentWidth - 2), 1);
    }
    else {
        newWidth = Math.min(Math.max(currentWidth * 1.05, currentWidth + 2), maxWidth);
    }
    var newHeight = Math.max(currentWidth * zoomRatio, 1);
    if (newHeight > maxHeight) {
        newWidth = maxHeight / zoomRatio;
        newHeight = maxHeight;
    }
    var left = parseInt(slider.css('left').replace('px', ''));
    left = Math.max(0, left + parseInt((currentWidth - newWidth) / 2));
    if ((left + newWidth) > maxWidth) {
        left = maxWidth - newWidth;
    }
    var top = parseInt(slider.css('top').replace('px', ''));
    top = Math.max(0, top + parseInt((currentHeight - newHeight) / 2));
    if ((top + newHeight) > maxHeight) {
        top = maxHeight - newHeight;
    }
    slider.css('left', left + 'px');
    slider.css('top', top + 'px');
    slider.width(parseInt(newWidth) +'px');
    slider.height(parseInt(newHeight) +'px');
    clearTimeout(zoomTimeout);
    zoomTimeout = setTimeout(function() { updatezoomWindow() }, 500);
}

function updatezoomWindow()
{
    var slider = $('#minimap .slider');
    var x = Math.floor(slider.position().left - slider.parent().position().left);
    var y = Math.floor(slider.position().top - slider.parent().position().top);
    var width = slider.width();
    var height = slider.height();
    var boundingBox = computeBoundingBox(x, y, width, height)
    getNodes(boundingBox, drawZoom);
}

// This function translates from one representation of a bounding box in gui coordinates to
// coordinates expected by the REST api. It takes x, y, width and height arguments and returns
// an object with left right top and bottom properties.
function computeBoundingBox(x, y, width, height)
{
	return {
            'xleft': x * ratio,
            'xright': (x + width) * ratio,
            'ytop': y / yZoom * ratio,
            'ybtm': (y + height) / yZoom * ratio
        }
}

function getNodes(boundingBox, callback) {
    $.ajax({
        url: url + '../getnodes',
        dataType: 'JSON',
        type: 'GET',
        data: boundingBox
    }).done(function(data) {
        callback(data);
    });
}

function initialize() {
    initializeMinimap();
    initializeZoomWindow();
}

function initializeMinimap() {
    $.ajax({
        url: url + 'getdimensions',
        dataType: 'JSON',
        type: 'GET'
    }).done(function(data) {
        var minimap = $('#minimap');
        var height = minimap.width() * (data.height / data.width);
        if (height < minHeight) {
            yZoom = Math.floor(minHeight / height);
            height *= yZoom;
        }

        minimap.height(height);

        var slider = minimap.find('.slider');

        ratio = data.width / minimap.width();

        minimap.prepend(
            $('<canvas/>', {'class':'genomeCanvas', Width: minimap.width(), Height: minimap.height() })
        );
        var boundingBox = computeBoundingBox(0, 0, minimap.width(), minimap.height());
        getNodes(boundingBox, drawGenome);
        zoom(-1);
    });
}

function initializeZoomWindow() {
    var zoomWindow = $('#zoomWindow');
    zoomWindow.prepend(
        $('<canvas/>', {'class':'zoomedCanvas', Width: zoomWindow.width(), Height: zoomWindow.height() })
    );
}<commit_msg>updated the urls in main.js to point to the new REST endpoints<commit_after>
var animationSpeed = 1000;
var currentHover = null;
var zoomTimeout = null;
var url = 'http://localhost:9998/';
var ratio = 0;
var minHeight = 300;
var yZoom = 1;

$('document').ready(function() {

    //Update splashscreen
    splashScreen(function() {
        $('body').bind('mousewheel DOMMouseScroll', function(e) {
            if ($(currentHover).hasClass('minimap')) {
                e.preventDefault();
                zoom(e.originalEvent.wheelDelta);
            }
        });

        //Add the slider
        var slider = $('#minimap').find('.slider');
        $(slider)
            .draggable({
                containment: "parent",
                stop: function() {
                    clearTimeout(zoomTimeout);
                    zoomTimeout = setTimeout(function() { updatezoomWindow() }, 500);
                }
            });

        $('.genome').hover(function() {
            currentHover = this;
        }, function() {
            currentHover = null;
        });

        initialize();
    });
});

function splashScreen(callback)
{
    $('#splashscreen')
        .fadeIn(animationSpeed)
        .animate({'left':'0', 'top':'0', 'width':'300px'}, animationSpeed, function() {
            var width = ($( window ).width() - 2) + 'px';
            $('#header').show().animate({'left':'0', 'top':'0', 'width':width}, animationSpeed, function() {
                $('#container').fadeIn();
                callback();
            })
        });
}

function pxToInt(css) {
    return parseFloat(css.replace('px', ''));
}

var drawZoom = function(nodes) {
    draw(nodes, $('#zoomWindow canvas')[0], function(x, y) {
        var slider = $('#minimap .slider');
        var sliderRatio = $('#zoomWindow').width() / slider.width();
        return {
            x: (x / ratio - pxToInt(slider.css('left'))) * sliderRatio,
            y: (y / ratio - pxToInt(slider.css('top'))) * sliderRatio
        }
    });
};

var drawGenome = function(nodes) {
    draw(nodes, $('#minimap canvas')[0], function(x, y) {
        return {
            x: x / ratio,
            y: y / ratio
        }
    });
};

function draw(data, c, translate) {
    var points = data.cList;
    var ctx = c.getContext("2d");
    ctx.clearRect(0, 0, c.width, c.height);
    $.each(points, function(id, value) {
        ctx.beginPath();
        var coor = translate(value.x, value.y);

        ctx.arc(coor.x, coor.y, value.weight / 10, 0, 2 * Math.PI);
        ctx.stroke();
        $.each(value.edges, function(key, edge) {
            if (edge.targetX != -1 && edge.targetY != -1) {
                ctx.beginPath();
                ctx.moveTo(coor.x, coor.y);
                var targetCoor = translate(edge.targetX, edge.targetY);
                ctx.lineTo(targetCoor.x, targetCoor.y);
                ctx.lineWidth = edge.weight;
                ctx.stroke();
            }
        });
    });
}

function zoom(direction) {
    var slider = $('.slider', currentHover);
    var minimap = $('#minimap');
    var zoomWindow = $('#zoomWindow');
    var currentWidth = slider.width();
    var currentHeight = slider.height();
    var maxWidth = minimap.width();
    var maxHeight = minimap.height();
    var zoomRatio = zoomWindow.height() / zoomWindow.width();
    var newWidth = 0;
    if (direction > 0) {
        newWidth = Math.max(Math.min(currentWidth * 0.95, currentWidth - 2), 1);
    }
    else {
        newWidth = Math.min(Math.max(currentWidth * 1.05, currentWidth + 2), maxWidth);
    }
    var newHeight = Math.max(currentWidth * zoomRatio, 1);
    if (newHeight > maxHeight) {
        newWidth = maxHeight / zoomRatio;
        newHeight = maxHeight;
    }
    var left = parseInt(slider.css('left').replace('px', ''));
    left = Math.max(0, left + parseInt((currentWidth - newWidth) / 2));
    if ((left + newWidth) > maxWidth) {
        left = maxWidth - newWidth;
    }
    var top = parseInt(slider.css('top').replace('px', ''));
    top = Math.max(0, top + parseInt((currentHeight - newHeight) / 2));
    if ((top + newHeight) > maxHeight) {
        top = maxHeight - newHeight;
    }
    slider.css('left', left + 'px');
    slider.css('top', top + 'px');
    slider.width(parseInt(newWidth) +'px');
    slider.height(parseInt(newHeight) +'px');
    clearTimeout(zoomTimeout);
    zoomTimeout = setTimeout(function() { updatezoomWindow() }, 500);
}

function updatezoomWindow()
{
    var slider = $('#minimap .slider');
    var x = Math.floor(slider.position().left - slider.parent().position().left);
    var y = Math.floor(slider.position().top - slider.parent().position().top);
    var width = slider.width();
    var height = slider.height();
    var boundingBox = computeBoundingBox(x, y, width, height)
    getNodes(boundingBox, drawZoom);
}

// This function translates from one representation of a bounding box in gui coordinates to
// coordinates expected by the REST api. It takes x, y, width and height arguments and returns
// an object with left right top and bottom properties.
function computeBoundingBox(x, y, width, height)
{
	return {
            'xleft': x * ratio,
            'xright': (x + width) * ratio,
            'ytop': y / yZoom * ratio,
            'ybtm': (y + height) / yZoom * ratio
        }
}

function getNodes(boundingBox, callback) {
    $.ajax({
        url: url + 'api/getnodes',
        dataType: 'JSON',
        type: 'GET',
        data: boundingBox
    }).done(function(data) {
        callback(data);
    });
}

function initialize() {
    initializeMinimap();
    initializeZoomWindow();
}

function initializeMinimap() {
    $.ajax({
        url: url + 'api/getdimensions',
        dataType: 'JSON',
        type: 'GET'
    }).done(function(data) {
        var minimap = $('#minimap');
        var height = minimap.width() * (data.height / data.width);
        if (height < minHeight) {
            yZoom = Math.floor(minHeight / height);
            height *= yZoom;
        }

        minimap.height(height);

        var slider = minimap.find('.slider');

        ratio = data.width / minimap.width();

        minimap.prepend(
            $('<canvas/>', {'class':'genomeCanvas', Width: minimap.width(), Height: minimap.height() })
        );
        var boundingBox = computeBoundingBox(0, 0, minimap.width(), minimap.height());
        getNodes(boundingBox, drawGenome);
        zoom(-1);
    });
}

function initializeZoomWindow() {
    var zoomWindow = $('#zoomWindow');
    zoomWindow.prepend(
        $('<canvas/>', {'class':'zoomedCanvas', Width: zoomWindow.width(), Height: zoomWindow.height() })
    );
}<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const logger = require('winston');
const snuze = require('snuze');
const getTaskConfig = require('./get-task-config');
const errno = require('errno');

let spawnSync = require('npm-run').spawnSync;

class TaskRunner {
  constructor(task, vars, opts) {
    this.task = task;
    this.vars = vars;
    this.opts = opts;
    this.attempts = 0;
  }

  execute() {
    logger.verbose('Starting task');
    const tasks = _.filter(this.task, (t) => t.cmd || t.task);

    _.forEach(tasks, command => {
      this.attempts = 0;

      if (command.cmd) {
        return this.runCommand(command);
      }
      else if (command.task) {
        if (command.for_all) {
          const vars = command.vars || {};
          return _.map(command.for_all, (x) => this.runTask(command.task, _.merge(vars, x)));
        }
        else {
          return this.runTask(command.task, command.vars);
        }
      }
      else {
        throw new Error('Task step contains no valid command');
      }
    });
    logger.verbose('Task completed successfully');
  }

  logCommand(parsedCommand, spawnOptions) {
    const command = parsedCommand.join(' ');
    const env = _.toPairs(spawnOptions.env)
      .map(x => `\n -${x[0]}=${x[1]}`)
      .join('');
    logger.info(`Executing command : ${command}`);
    logger.verbose(`Environment variables: ${env}`);
  }

  runTask(task, vars) {
    const cmdVars = _.mapValues(vars, this.expandTokens.bind(this));
    const taskVars = _.merge(this.vars, cmdVars);
    logger.info(`Executing task : ${task} with vars ${JSON.stringify(taskVars)}`);

    const taskConfig = getTaskConfig(task, taskVars, this.opts);
    const taskRunner = new TaskRunner(taskConfig.task, taskConfig.vars, this.opts);

    return taskRunner.execute();
  }

  extractArgs(cmd) {
    const parsedCommand = this.expandTokens(cmd).match(/["'][^"']+["']|\S+/g);

    return _.map(parsedCommand, pc => pc.replace(/['"]/g, ''));
  }

  runCommand(command) {
    // const parsedCommandl = this.expandTokens(command.cmd).split(' ');
    const parsedCommand = this.extractArgs(command.cmd);
    const parsedEnv = this.resolveKeyValuePairs(command.environment);
    const spawnOptions = this.buildSpawnOptions(command, parsedEnv);

    this.logCommand(parsedCommand, spawnOptions);

    this.attempts++;
    const executable = parsedCommand[0];
    const result = spawnSync(executable, _.tail(parsedCommand), spawnOptions);

    if (command.register && result.stdout) {
      const out = result.stdout.toString().trim();
      logger.info(`Saved ${out} to ${command.register}`);
      this.vars[command.register] = out;
    }

    if (this.shouldExecutionContinue(result, command)) {
      return true;
    }

    if (this.shouldCommandRetry(command)) {
      snuze.snooze(this.delayInMilliseconds(command.retry.delay));
      return this.runCommand(command);
    }

    logger.error(`Command exited with non-zero exit status [${result.error.code}]. Aborting.`);
    if (errno.code[result.error.code]) {
      logger.error(`Error description: ${errno.code[result.error.code].description}`);
    }
    logger.error(`Check the executable ${executable} exists.`);
    throw result.error;
  }

  shouldExecutionContinue(result, command) {
    return result.status === 0 || command.ignore_errors;
  }

  shouldCommandRetry(command) {
    return command.retry && this.attempts < command.retry.attempts;
  }

  expandTokens(str) {
    logger.verbose('Interpolating ERB tokens');
    const template = _.template(str);

    return template(this.vars);
  }

  resolveKeyValuePairs(array) {
    const templated = _.map(array, a => this.expandTokens(a, this.vars));
    const split = _.map(templated, a => a.split('='));
    const hashed = _.fromPairs(split);
    return hashed;
  }

  buildSpawnOptions(command, envOptions) {
    const stdout = command.register || this.opts.quiet ? 'pipe' : process.stdout;
    const env = _.isEmpty(envOptions) ? process.env : _.merge(envOptions, process.env);
    return {
      stdio: [process.stdin, stdout, process.stderr],
      env: env
    };
  }

  delayInMilliseconds(delay) {
    return delay * 1000;
  }
}

module.exports = TaskRunner;
<commit_msg>Guarding against undefined errors in result<commit_after>'use strict';

const _ = require('lodash');
const logger = require('winston');
const snuze = require('snuze');
const getTaskConfig = require('./get-task-config');
const errno = require('errno');

let spawnSync = require('npm-run').spawnSync;

class TaskRunner {
  constructor(task, vars, opts) {
    this.task = task;
    this.vars = vars;
    this.opts = opts;
    this.attempts = 0;
  }

  execute() {
    logger.verbose('Starting task');
    const tasks = _.filter(this.task, (t) => t.cmd || t.task);

    _.forEach(tasks, command => {
      this.attempts = 0;

      if (command.cmd) {
        return this.runCommand(command);
      }
      else if (command.task) {
        if (command.for_all) {
          const vars = command.vars || {};
          return _.map(command.for_all, (x) => this.runTask(command.task, _.merge(vars, x)));
        }
        else {
          return this.runTask(command.task, command.vars);
        }
      }
      else {
        throw new Error('Task step contains no valid command');
      }
    });
    logger.verbose('Task completed successfully');
  }

  logCommand(parsedCommand, spawnOptions) {
    const command = parsedCommand.join(' ');
    const env = _.toPairs(spawnOptions.env)
      .map(x => `\n -${x[0]}=${x[1]}`)
      .join('');
    logger.info(`Executing command : ${command}`);
    logger.verbose(`Environment variables: ${env}`);
  }

  runTask(task, vars) {
    const cmdVars = _.mapValues(vars, this.expandTokens.bind(this));
    const taskVars = _.merge(this.vars, cmdVars);
    logger.info(`Executing task : ${task} with vars ${JSON.stringify(taskVars)}`);

    const taskConfig = getTaskConfig(task, taskVars, this.opts);
    const taskRunner = new TaskRunner(taskConfig.task, taskConfig.vars, this.opts);

    return taskRunner.execute();
  }

  extractArgs(cmd) {
    const parsedCommand = this.expandTokens(cmd).match(/["'][^"']+["']|\S+/g);

    return _.map(parsedCommand, pc => pc.replace(/['"]/g, ''));
  }

  runCommand(command) {
    // const parsedCommandl = this.expandTokens(command.cmd).split(' ');
    const parsedCommand = this.extractArgs(command.cmd);
    const parsedEnv = this.resolveKeyValuePairs(command.environment);
    const spawnOptions = this.buildSpawnOptions(command, parsedEnv);

    this.logCommand(parsedCommand, spawnOptions);

    this.attempts++;
    const executable = parsedCommand[0];
    const result = spawnSync(executable, _.tail(parsedCommand), spawnOptions);

    if (command.register && result.stdout) {
      const out = result.stdout.toString().trim();
      logger.info(`Saved ${out} to ${command.register}`);
      this.vars[command.register] = out;
    }

    if (this.shouldExecutionContinue(result, command)) {
      return true;
    }

    if (this.shouldCommandRetry(command)) {
      snuze.snooze(this.delayInMilliseconds(command.retry.delay));
      return this.runCommand(command);
    }

    if (result.error) {
      logger.error(`Command exited with non-zero exit status [${result.error.code}]. Aborting.`);
    }
    else {
      logger.error('Command exited with error. Aborting.');
    }

    if (errno.code[result.error.code]) {
      logger.error(`Error description: ${errno.code[result.error.code].description}`);
      logger.error(`Check the executable ${executable} exists.`);
    }

    if (result.error) {
      throw result.error;
    }
    else {
      throw new Error(`Command ${command.cmd} exited with error.`);
    }
  }

  shouldExecutionContinue(result, command) {
    return result.status === 0 || command.ignore_errors;
  }

  shouldCommandRetry(command) {
    return command.retry && this.attempts < command.retry.attempts;
  }

  expandTokens(str) {
    logger.verbose('Interpolating ERB tokens');
    const template = _.template(str);

    return template(this.vars);
  }

  resolveKeyValuePairs(array) {
    const templated = _.map(array, a => this.expandTokens(a, this.vars));
    const split = _.map(templated, a => a.split('='));
    const hashed = _.fromPairs(split);
    return hashed;
  }

  buildSpawnOptions(command, envOptions) {
    const stdout = command.register || this.opts.quiet ? 'pipe' : process.stdout;
    const env = _.isEmpty(envOptions) ? process.env : _.merge(envOptions, process.env);
    return {
      stdio: [process.stdin, stdout, process.stderr],
      env: env
    };
  }

  delayInMilliseconds(delay) {
    return delay * 1000;
  }
}

module.exports = TaskRunner;
<|endoftext|>


<commit_before>
// path to test base, without a slash, e.g. '../../common/dot/tests/qunit'
function runDotTests( pathToTestBase ) {
  function loadTestFile( src ) {
    var script = document.createElement( 'script' );
    script.type = 'text/javascript';

    // make sure things aren't cached, just in case
    script.src = pathToTestBase + '/' + src + '?random=' + Math.random().toFixed( 10 );

    var other = document.getElementsByTagName( 'script' )[0];
    other.parentNode.insertBefore( script, other );
  }

  loadTestFile( 'js/simple-tests.js' );
};
<commit_msg>In-order loading of unit tests<commit_after>
// path to test base, without a slash, e.g. '../../common/dot/tests/qunit'
function runDotTests( pathToTestBase ) {
  function loadTestFile( src ) {
    var script = document.createElement( 'script' );
    script.type = 'text/javascript';

    // make sure things aren't cached, just in case
    script.src = pathToTestBase + '/' + src + '?random=' + Math.random().toFixed( 10 );

    document.getElementsByTagName( 'head' )[0].appendChild( script );
  }

  loadTestFile( 'js/simple-tests.js' );
};
<|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt) {

	var zipOutputFolder = grunt.option('target') || 'target';
	var srcFileSet = [ {
		expand : true,
		cwd : 'src/',
		src : [ '**' ],
		dest : zipOutputFolder + '/dist/'
	} ];
	var distFileSet = [ {
		expand : true,
		cwd : zipOutputFolder + '/dist/',
		src : [ '**' ],
		dest : '/'
	} ];

	grunt.config.set('pkg', grunt.file.readJSON('package.json'));
	var gadgetVersion = grunt.config.get('pkg.version');
	if (!gadgetVersion) {
		gadgetVersion = "0.9";
	}
	gadgetVersion = gadgetVersion.replace(/^(\d+\.\d+).*/, "$1");
	grunt.config.set('pkg.gadget_version', gadgetVersion);
	grunt.config.set('compress', {
		main : {
			options : {
				archive : zipOutputFolder + '/<%= pkg.name %>.zip'
			},
			files : distFileSet
		}
	});
	grunt.config.set('copy', {
		main : {
			options : {
				process : function(content, srcpath) {
					return grunt.template.process(content);
				}
			},
			files : srcFileSet
		}
	});

	grunt.loadNpmTasks('grunt-contrib-copy');
	grunt.loadNpmTasks('grunt-contrib-compress');
	grunt.loadNpmTasks('grunt-maven-tasks');

	grunt.registerTask('uptime-gadget:compress', [ "copy", "compress" ]);
	grunt.registerTask('uptime-gadget:mavenDeploy', [ "setMavenTaskOptions", "copy", "maven:deploy" ]);
	grunt.registerTask('uptime-gadget:mavenRelease', [ "setMavenTaskOptions", "copy", "maven:release" ]);

	grunt.registerTask('setMavenTaskOptions', 'Loads mavenOptions.json and populates config for grunt-maven-tasks plugin',
			function() {
				var mavenUserConfigFilename = "userOptions/mavenOptions.json";
				if (!grunt.file.exists(mavenUserConfigFilename)) {
					grunt.fatal("Missing the required " + mavenUserConfigFilename + " config file.");
					return;
				}
				grunt.config.set("mavenUserOptions", grunt.file.readJSON(mavenUserConfigFilename));
				grunt.config.requires("mavenUserOptions.groupId", "mavenUserOptions.snapshotRepositoryId",
						"mavenUserOptions.snapshotUrl", "mavenUserOptions.releaseRepositoryId", "mavenUserOptions.releaseUrl");
				grunt.config.set('maven', {
					options : {
						groupId : '<%= mavenUserOptions.groupId %>',
						file : 'target/<%= pkg.name %>.zip',
						injectDestFolder : false
					},
					deploy : {
						options : {
							repositoryId : '<%= mavenUserOptions.snapshotRepositoryId %>',
							url : '<%= mavenUserOptions.snapshotUrl %>'
						},
						files : distFileSet
					},
					release : {
						options : {
							repositoryId : '<%= mavenUserOptions.releaseRepositoryId %>',
							url : '<%= mavenUserOptions.releaseUrl %>'
						},
						files : distFileSet
					}
				});
			});

};
<commit_msg>UTS-1774 Skip processing .gif, .png, and .jpg files.<commit_after>'use strict';

module.exports = function(grunt) {

	var zipOutputFolder = grunt.option('target') || 'target';
	var srcFileSet = [ {
		expand : true,
		cwd : 'src/',
		src : [ '**' ],
		dest : zipOutputFolder + '/dist/'
	} ];
	var distFileSet = [ {
		expand : true,
		cwd : zipOutputFolder + '/dist/',
		src : [ '**' ],
		dest : '/'
	} ];

	grunt.config.set('pkg', grunt.file.readJSON('package.json'));
	var gadgetVersion = grunt.config.get('pkg.version');
	if (!gadgetVersion) {
		gadgetVersion = "0.9";
	}
	gadgetVersion = gadgetVersion.replace(/^(\d+\.\d+).*/, "$1");
	grunt.config.set('pkg.gadget_version', gadgetVersion);
	grunt.config.set('compress', {
		main : {
			options : {
				archive : zipOutputFolder + '/<%= pkg.name %>.zip'
			},
			files : distFileSet
		}
	});
	grunt.config.set('copy', {
		main : {
			options : {
				process : function(content, srcpath) {
					return grunt.template.process(content);
				},
				noProcess : [ '**/*.png', '**/*.gif', '**/*.jpg' ]
			},
			files : srcFileSet
		}
	});

	grunt.loadNpmTasks('grunt-contrib-copy');
	grunt.loadNpmTasks('grunt-contrib-compress');
	grunt.loadNpmTasks('grunt-maven-tasks');

	grunt.registerTask('uptime-gadget:compress', [ "copy", "compress" ]);
	grunt.registerTask('uptime-gadget:mavenDeploy', [ "setMavenTaskOptions", "copy", "maven:deploy" ]);
	grunt.registerTask('uptime-gadget:mavenRelease', [ "setMavenTaskOptions", "copy", "maven:release" ]);

	grunt.registerTask('setMavenTaskOptions', 'Loads mavenOptions.json and populates config for grunt-maven-tasks plugin',
			function() {
				var mavenUserConfigFilename = "userOptions/mavenOptions.json";
				if (!grunt.file.exists(mavenUserConfigFilename)) {
					grunt.fatal("Missing the required " + mavenUserConfigFilename + " config file.");
					return;
				}
				grunt.config.set("mavenUserOptions", grunt.file.readJSON(mavenUserConfigFilename));
				grunt.config.requires("mavenUserOptions.groupId", "mavenUserOptions.snapshotRepositoryId",
						"mavenUserOptions.snapshotUrl", "mavenUserOptions.releaseRepositoryId", "mavenUserOptions.releaseUrl");
				grunt.config.set('maven', {
					options : {
						groupId : '<%= mavenUserOptions.groupId %>',
						file : 'target/<%= pkg.name %>.zip',
						injectDestFolder : false
					},
					deploy : {
						options : {
							repositoryId : '<%= mavenUserOptions.snapshotRepositoryId %>',
							url : '<%= mavenUserOptions.snapshotUrl %>'
						},
						files : distFileSet
					},
					release : {
						options : {
							repositoryId : '<%= mavenUserOptions.releaseRepositoryId %>',
							url : '<%= mavenUserOptions.releaseUrl %>'
						},
						files : distFileSet
					}
				});
			});

};
<|endoftext|>


<commit_before>/* Development settings */
define(['require'], function (require) {
    var scriptUrl = require.toUrl('dist/nainwaklet.js'),
        hubUrl = require.toUrl('../../hub.html');

    function getImageUrl(name) {
        return require.toUrl('images/' + name);
    }

    function getCssUrl(name) {
        return require.toUrl('css/' + name);
    }

    return {
        scriptUrl: scriptUrl,
        hubUrl: hubUrl,
        channel: 'default',
        getImageUrl: getImageUrl,
        getCssUrl: getCssUrl
    };
});
<commit_msg>Fixed the urls in the development settings<commit_after>/* Development settings */
define(['require', 'utils/url'], function (require, url) {
    function fullUrl(path) {
        var relativeUrl = require.toUrl(path),
            location = url.parse(relativeUrl);
        return location.href;
    }

    var scriptUrl = fullUrl('dist/nainwaklet.js');

    function getImageUrl(name) {
        return fullUrl('images/' + name);
    }

    function getCssUrl(name) {
        return fullUrl('css/' + name);
    }

    return {
        channel: 'default',
        scriptUrl: scriptUrl,
        getImageUrl: getImageUrl,
        getCssUrl: getCssUrl
    };
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const ReactNative = require('ReactNative');
const Platform = require('Platform');
const React = require('React');
const StyleSheet = require('StyleSheet');

const requireNativeComponent = require('requireNativeComponent');

import type {ImageSource} from 'ImageSource';
import type {ViewStyleProp} from 'StyleSheet';
import type {ColorValue} from 'StyleSheetTypes';
import type {ViewProps} from 'ViewPropTypes';

const RCTSlider = requireNativeComponent('RCTSlider');

type Event = Object;

type IOSProps = $ReadOnly<{|
  /**
   * Assigns a single image for the track. Only static images are supported.
   * The center pixel of the image will be stretched to fill the track.
   */
  trackImage?: ?ImageSource,

  /**
   * Assigns a minimum track image. Only static images are supported. The
   * rightmost pixel of the image will be stretched to fill the track.
   */
  minimumTrackImage?: ?ImageSource,

  /**
   * Assigns a maximum track image. Only static images are supported. The
   * leftmost pixel of the image will be stretched to fill the track.
   */
  maximumTrackImage?: ?ImageSource,

  /**
   * Sets an image for the thumb. Only static images are supported.
   */
  thumbImage?: ?ImageSource,
|}>;

type AndroidProps = $ReadOnly<{|
  /**
   * Color of the foreground switch grip.
   * @platform android
   */
  thumbTintColor?: ?ColorValue,
|}>;

type Props = $ReadOnly<{|
  ...ViewProps,
  ...IOSProps,
  ...AndroidProps,

  /**
   * Used to style and layout the `Slider`.  See `StyleSheet.js` and
   * `ViewStylePropTypes.js` for more info.
   */
  style?: ?ViewStyleProp,

  /**
   * Initial value of the slider. The value should be between minimumValue
   * and maximumValue, which default to 0 and 1 respectively.
   * Default value is 0.
   *
   * *This is not a controlled component*, you don't need to update the
   * value during dragging.
   */
  value?: ?number,

  /**
   * Step value of the slider. The value should be
   * between 0 and (maximumValue - minimumValue).
   * Default value is 0.
   */
  step?: ?number,

  /**
   * Initial minimum value of the slider. Default value is 0.
   */
  minimumValue?: ?number,

  /**
   * Initial maximum value of the slider. Default value is 1.
   */
  maximumValue?: ?number,

  /**
   * The color used for the track to the left of the button.
   * Overrides the default blue gradient image on iOS.
   */
  minimumTrackTintColor?: ?ColorValue,

  /**
   * The color used for the track to the right of the button.
   * Overrides the default blue gradient image on iOS.
   */
  maximumTrackTintColor?: ?ColorValue,

  /**
   * If true the user won't be able to move the slider.
   * Default value is false.
   */
  disabled?: ?boolean,

  /**
   * Callback continuously called while the user is dragging the slider.
   */
  onValueChange?: ?Function,

  /**
   * Callback that is called when the user releases the slider,
   * regardless if the value has changed. The current value is passed
   * as an argument to the callback handler.
   */
  onSlidingComplete?: ?Function,

  /**
   * Used to locate this view in UI automation tests.
   */
  testID?: ?string,
|}>;

/**
 * A component used to select a single value from a range of values.
 *
 * ### Usage
 *
 * The example below shows how to use `Slider` to change
 * a value used by `Text`. The value is stored using
 * the state of the root component (`App`). The same component
 * subscribes to the `onValueChange`  of `Slider` and changes
 * the value using `setState`.
 *
 *```
 * import React from 'react';
 * import { StyleSheet, Text, View, Slider } from 'react-native';
 *
 * export default class App extends React.Component {
 *   constructor(props) {
 *     super(props);
 *     this.state = {
 *       value: 50
 *     }
 *   }
 *
 *   change(value) {
 *     this.setState(() => {
 *       return {
 *         value: parseFloat(value)
 *       };
 *     });
 *   }
 *
 *   render() {
 *     const {value} = this.state;
 *     return (
 *       <View style={styles.container}>
 *         <Text style={styles.text}>{String(value)}</Text>
 *         <Slider
 *           step={1}
 *           maximumValue={100}
 *           onValueChange={this.change.bind(this)}
 *           value={value} />
 *       </View>
 *     );
 *   }
 * }
 *
 * const styles = StyleSheet.create({
 *   container: {
 *     flex: 1,
 *     flexDirection: 'column',
 *     justifyContent: 'center'
 *   },
 *   text: {
 *     fontSize: 50,
 *     textAlign: 'center'
 *   }
 * });
 *```
 *
 */
const Slider = (
  props: $ReadOnly<{|
    ...Props,
    forwardedRef?: ?React.Ref<'RCTActivityIndicatorView'>,
  |}>,
) => {
  const style = StyleSheet.compose(
    styles.slider,
    props.style,
  );

  const onValueChange =
    props.onValueChange &&
    ((event: Event) => {
      let userEvent = true;
      if (Platform.OS === 'android') {
        // On Android there's a special flag telling us the user is
        // dragging the slider.
        userEvent = event.nativeEvent.fromUser;
      }
      props.onValueChange &&
        userEvent &&
        props.onValueChange(event.nativeEvent.value);
    });

  const onChange = onValueChange;

  const onSlidingComplete =
    props.onSlidingComplete &&
    ((event: Event) => {
      props.onSlidingComplete &&
        props.onSlidingComplete(event.nativeEvent.value);
    });

  return (
    <RCTSlider
      {...props}
      style={style}
      onChange={onChange}
      onSlidingComplete={onSlidingComplete}
      onValueChange={onValueChange}
      enabled={!props.disabled}
      onStartShouldSetResponder={() => true}
      onResponderTerminationRequest={() => false}
    />
  );
};

// $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet.
const SliderWithRef = React.forwardRef((props: Props, ref) => {
  return <Slider {...props} forwardedRef={ref} />;
});

SliderWithRef.defaultProps = {
  disabled: false,
  value: 0,
  minimumValue: 0,
  maximumValue: 1,
  step: 0,
};
SliderWithRef.displayName = 'Slider';

let styles;
if (Platform.OS === 'ios') {
  styles = StyleSheet.create({
    slider: {
      height: 40,
    },
  });
} else {
  styles = StyleSheet.create({
    slider: {},
  });
}

module.exports = (SliderWithRef: Class<ReactNative.NativeComponent<Props>>);
<commit_msg>Passing forwardedRef to Slider<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const ReactNative = require('ReactNative');
const Platform = require('Platform');
const React = require('React');
const StyleSheet = require('StyleSheet');

const requireNativeComponent = require('requireNativeComponent');

import type {ImageSource} from 'ImageSource';
import type {ViewStyleProp} from 'StyleSheet';
import type {ColorValue} from 'StyleSheetTypes';
import type {ViewProps} from 'ViewPropTypes';

const RCTSlider = requireNativeComponent('RCTSlider');

type Event = Object;

type IOSProps = $ReadOnly<{|
  /**
   * Assigns a single image for the track. Only static images are supported.
   * The center pixel of the image will be stretched to fill the track.
   */
  trackImage?: ?ImageSource,

  /**
   * Assigns a minimum track image. Only static images are supported. The
   * rightmost pixel of the image will be stretched to fill the track.
   */
  minimumTrackImage?: ?ImageSource,

  /**
   * Assigns a maximum track image. Only static images are supported. The
   * leftmost pixel of the image will be stretched to fill the track.
   */
  maximumTrackImage?: ?ImageSource,

  /**
   * Sets an image for the thumb. Only static images are supported.
   */
  thumbImage?: ?ImageSource,
|}>;

type AndroidProps = $ReadOnly<{|
  /**
   * Color of the foreground switch grip.
   * @platform android
   */
  thumbTintColor?: ?ColorValue,
|}>;

type Props = $ReadOnly<{|
  ...ViewProps,
  ...IOSProps,
  ...AndroidProps,

  /**
   * Used to style and layout the `Slider`.  See `StyleSheet.js` and
   * `ViewStylePropTypes.js` for more info.
   */
  style?: ?ViewStyleProp,

  /**
   * Initial value of the slider. The value should be between minimumValue
   * and maximumValue, which default to 0 and 1 respectively.
   * Default value is 0.
   *
   * *This is not a controlled component*, you don't need to update the
   * value during dragging.
   */
  value?: ?number,

  /**
   * Step value of the slider. The value should be
   * between 0 and (maximumValue - minimumValue).
   * Default value is 0.
   */
  step?: ?number,

  /**
   * Initial minimum value of the slider. Default value is 0.
   */
  minimumValue?: ?number,

  /**
   * Initial maximum value of the slider. Default value is 1.
   */
  maximumValue?: ?number,

  /**
   * The color used for the track to the left of the button.
   * Overrides the default blue gradient image on iOS.
   */
  minimumTrackTintColor?: ?ColorValue,

  /**
   * The color used for the track to the right of the button.
   * Overrides the default blue gradient image on iOS.
   */
  maximumTrackTintColor?: ?ColorValue,

  /**
   * If true the user won't be able to move the slider.
   * Default value is false.
   */
  disabled?: ?boolean,

  /**
   * Callback continuously called while the user is dragging the slider.
   */
  onValueChange?: ?Function,

  /**
   * Callback that is called when the user releases the slider,
   * regardless if the value has changed. The current value is passed
   * as an argument to the callback handler.
   */
  onSlidingComplete?: ?Function,

  /**
   * Used to locate this view in UI automation tests.
   */
  testID?: ?string,
|}>;

/**
 * A component used to select a single value from a range of values.
 *
 * ### Usage
 *
 * The example below shows how to use `Slider` to change
 * a value used by `Text`. The value is stored using
 * the state of the root component (`App`). The same component
 * subscribes to the `onValueChange`  of `Slider` and changes
 * the value using `setState`.
 *
 *```
 * import React from 'react';
 * import { StyleSheet, Text, View, Slider } from 'react-native';
 *
 * export default class App extends React.Component {
 *   constructor(props) {
 *     super(props);
 *     this.state = {
 *       value: 50
 *     }
 *   }
 *
 *   change(value) {
 *     this.setState(() => {
 *       return {
 *         value: parseFloat(value)
 *       };
 *     });
 *   }
 *
 *   render() {
 *     const {value} = this.state;
 *     return (
 *       <View style={styles.container}>
 *         <Text style={styles.text}>{String(value)}</Text>
 *         <Slider
 *           step={1}
 *           maximumValue={100}
 *           onValueChange={this.change.bind(this)}
 *           value={value} />
 *       </View>
 *     );
 *   }
 * }
 *
 * const styles = StyleSheet.create({
 *   container: {
 *     flex: 1,
 *     flexDirection: 'column',
 *     justifyContent: 'center'
 *   },
 *   text: {
 *     fontSize: 50,
 *     textAlign: 'center'
 *   }
 * });
 *```
 *
 */
const Slider = (
  props: Props,
  forwardedRef?: ?React.Ref<'RCTActivityIndicatorView'>,
) => {
  const style = StyleSheet.compose(
    styles.slider,
    props.style,
  );

  const onValueChange =
    props.onValueChange &&
    ((event: Event) => {
      let userEvent = true;
      if (Platform.OS === 'android') {
        // On Android there's a special flag telling us the user is
        // dragging the slider.
        userEvent = event.nativeEvent.fromUser;
      }
      props.onValueChange &&
        userEvent &&
        props.onValueChange(event.nativeEvent.value);
    });

  const onChange = onValueChange;

  const onSlidingComplete =
    props.onSlidingComplete &&
    ((event: Event) => {
      props.onSlidingComplete &&
        props.onSlidingComplete(event.nativeEvent.value);
    });

  return (
    <RCTSlider
      {...props}
      ref={forwardedRef}
      style={style}
      onChange={onChange}
      onSlidingComplete={onSlidingComplete}
      onValueChange={onValueChange}
      enabled={!props.disabled}
      onStartShouldSetResponder={() => true}
      onResponderTerminationRequest={() => false}
    />
  );
};

// $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet.
const SliderWithRef = React.forwardRef(Slider);

SliderWithRef.defaultProps = {
  disabled: false,
  value: 0,
  minimumValue: 0,
  maximumValue: 1,
  step: 0,
};
SliderWithRef.displayName = 'Slider';

let styles;
if (Platform.OS === 'ios') {
  styles = StyleSheet.create({
    slider: {
      height: 40,
    },
  });
} else {
  styles = StyleSheet.create({
    slider: {},
  });
}

module.exports = (SliderWithRef: Class<ReactNative.NativeComponent<Props>>);
<|endoftext|>


<commit_before>/**
 *
 * A wrapper library around the Porthole js library that adds some abstraction
 * around the setup and handling of communication.
 *
 * In your "parent" application page, you must have an IFrame configured as follows
 *
 *
 *
 * where
 *
 *	$ChildPageUrl is the url to the page being iframed
 *	$ChildPageProxyUrl is the URL to the proxy.html file that comes with porthole.js
 *			and has been configured with appropriate URL paths for porthole.js
 *
 * Then, from your javascript code somewhere, you can call the following (assuming
 * here that we're sending a call TO the child frame
 *
 * // this call must be made, as it sets up the appropriate data comms
 * // to the child frame
 * ProxyFrameManager.listenTo('FrameName');
 *
 * ProxyFrameManager.send('FrameName', 'methodName', 'param1', {and: 'so on'});
 *
 *
 * In the child page, you do something similar, but given there's no reference
 * to the frame name (there's only the 'parent'), you need to explicitly state
 * the URL for the parent frame's proxy url to listen on, as well as the
 * possible methods to handle the method calls
 *
 * // even though we're sending data
 * ProxyFrameManager.listenTo('$ParentPageProxyUrl', {
 *		methodName: function (param, objectParam) {
 *			// do something to handle the call here
 *		}
 * });
 *
 * Now, assuming you wanted to perform method calls BACK to parent from the
 * child page, you have a similar setup
 *
 * In the parent page:
 *
 * ProxyFrameManager.listenTo('FrameName', {
 *		parentMethod: function () {
 *
 *		}
 * });
 *
 * In the child page, the main difference in the way 'send' is called is
 * in how the target of the send is referenced - in this case, a 'parent' name
 * is automatically created for us
 *
 * // sets up the communication
 * ProxyFrameManager.listenTo('$ParentPageProxyUrl');
 *
 * // just send directly to the 'parent' of the frame
 * ProxyFrameManager.send('parent', 'parentMethod');
 *
 *
 */

(function ($) {

	/**
	 * A class for managing the sending and receiving of method calls down
	 * to a child frame.
	 */
	window.ProxyFrameManager = (function () {
		var frames = {};
		var loadedFrames = {};
		var deferred = {};

		/**
		 * Receives inbound messages and routes them to appropriate handlers
		 *
		 * Uses an inner function so that we can close over the frame name
		 * and route the message to the appropriate listeners
		 */
		var messageHandler = function (frameName) {
			return function (messageEvent) {

				if (messageEvent.data) {
					var details = JSON.parse(messageEvent.data);
					if (details && details.method) {
						var frame = getFrame(frameName);

						// first check the origin domain to make sure the
						// caller was who we expected
						if (frame.url.indexOf(messageEvent.origin) != 0) {
							if (console && console.log) {
								console.log("Invalid source domain " + messageEvent.origin + ': expected ' + frame.url);
							}
						}

						// now go through all listeners and send the message
						// if they have a method defined to handle it
						for (var i in frame.listeners) {
							var l = frame.listeners[i]
							if (l && l[details.method]) {
								var method = l[details.method];
								method.apply(l, details.args);
							}
						}
					}
				}
			}
		}

		/**
		 * Enables a named frame for communication. This is used for all
		 * communication to this named item
		 *
		 * @param String name
		 *				The name can be either an iframe element name, or the
		 *				string 'parent' to indicate that it's the parent
		 *				of this frame that we're interested in
		 */
		var getFrame = function (name, proxyUrl) {
			if (!frames[name]) {
				// see whether or not the frame exists. If so, we need
				// to use its data-proxy attribute for getting the proxy
				// location
				var elem = $('iframe[name=' + name + ']');
				elem.load(function () {
					deferredSend(name);
				})
				if ((elem && elem.length) || (name == 'parent' && proxyUrl)) {
					var windowProxy = null;
					if (proxyUrl) {
						windowProxy = new Porthole.WindowProxy(proxyUrl);
					} else {
						proxyUrl = elem.attr('data-proxy');
						windowProxy = new Porthole.WindowProxy(proxyUrl, name);
					}

					windowProxy.addEventListener(messageHandler(name));
					frames[name] = {
						url: proxyUrl,
						channel: windowProxy,
						listeners: []
					};
				}
			}
			return frames[name];
		}

		/**
		 * Register to listen to a particular frame. Any messages sent will be
		 * forwarded to the listener
		 *
		 * @param String frameName
		 *				The name of the frame we're passing communication to.
		 *				If we're the CHILD framed page, pass through the URL to
		 *				the PARENT page's proxy.html page. From that point
		 *				onwards, you can refer to the 'parent' frame in calls
		 *				to 'send'
		 */
		var listenTo = function (frameName, object) {
			var frame = null;
			if (frameName.indexOf('://') > 0) {
				frame = getFrame('parent', frameName);
			} else {
				frame = getFrame(frameName);
			}

			if (frame) {
				frame.listeners.push(object);
			}
		}

		/**
		 * Some 'send' requests are done before the framed page loads completely. This will
		 * send those messages
		 */
		var deferredSend = function (frameName) {
			loadedFrames[frameName] = true;
			if (deferred[frameName]) {
				for (var i in deferred[frameName]) {
					var method = deferred[frameName][i].method;
					var args = deferred[frameName][i].args;
					args.unshift(method); args.unshift(frameName);
					console.log("calling deferred send");
					console.log(args);
					send.apply(this, args);
				}
			}
		}

		/**
		 * Send a method call down to a particular named frame
		 */
		var send = function (frameName, method) {
			var args = Array.prototype.slice.call(arguments, 2);
			if (!loadedFrames[frameName]) {
				var list = deferred[frameName] != null ? deferred[frameName] : [];
				list.push({
					method: method,
					args: args
				});
				deferred[frameName] = list;
				return;
			}

			var frame = getFrame(frameName);
			if (frame) {
				// send data through the proxy, after first packaging things up
				var data = {
					method: method,
					args: args
				}

				data = JSON.stringify(data);
				frame.channel.postMessage(data);
			}
		}
		return {
			listenTo: listenTo,
			send: send
		}
	})();
})(jQuery);
<commit_msg>BUGFIX: Fixed issue where parent calls were indefinitely deferred<commit_after>/**
 *
 * A wrapper library around the Porthole js library that adds some abstraction
 * around the setup and handling of communication.
 *
 * In your "parent" application page, you must have an IFrame configured as follows
 *
 *
 *
 * where
 *
 *	$ChildPageUrl is the url to the page being iframed
 *	$ChildPageProxyUrl is the URL to the proxy.html file that comes with porthole.js
 *			and has been configured with appropriate URL paths for porthole.js
 *
 * Then, from your javascript code somewhere, you can call the following (assuming
 * here that we're sending a call TO the child frame
 *
 * // this call must be made, as it sets up the appropriate data comms
 * // to the child frame
 * ProxyFrameManager.listenTo('FrameName');
 *
 * ProxyFrameManager.send('FrameName', 'methodName', 'param1', {and: 'so on'});
 *
 *
 * In the child page, you do something similar, but given there's no reference
 * to the frame name (there's only the 'parent'), you need to explicitly state
 * the URL for the parent frame's proxy url to listen on, as well as the
 * possible methods to handle the method calls
 *
 * // even though we're sending data
 * ProxyFrameManager.listenTo('$ParentPageProxyUrl', {
 *		methodName: function (param, objectParam) {
 *			// do something to handle the call here
 *		}
 * });
 *
 * Now, assuming you wanted to perform method calls BACK to parent from the
 * child page, you have a similar setup
 *
 * In the parent page:
 *
 * ProxyFrameManager.listenTo('FrameName', {
 *		parentMethod: function () {
 *
 *		}
 * });
 *
 * In the child page, the main difference in the way 'send' is called is
 * in how the target of the send is referenced - in this case, a 'parent' name
 * is automatically created for us
 *
 * // sets up the communication
 * ProxyFrameManager.listenTo('$ParentPageProxyUrl');
 *
 * // just send directly to the 'parent' of the frame
 * ProxyFrameManager.send('parent', 'parentMethod');
 *
 *
 */

(function ($) {

	/**
	 * A class for managing the sending and receiving of method calls down
	 * to a child frame.
	 */
	window.ProxyFrameManager = (function () {
		var frames = {};
		var loadedFrames = {'parent': true};
		var deferred = {};

		/**
		 * Receives inbound messages and routes them to appropriate handlers
		 *
		 * Uses an inner function so that we can close over the frame name
		 * and route the message to the appropriate listeners
		 */
		var messageHandler = function (frameName) {
			return function (messageEvent) {

				if (messageEvent.data) {
					var details = JSON.parse(messageEvent.data);
					if (details && details.method) {
						var frame = getFrame(frameName);

						// first check the origin domain to make sure the
						// caller was who we expected
						if (frame.url.indexOf(messageEvent.origin) != 0) {
							if (console && console.log) {
								console.log("Invalid source domain " + messageEvent.origin + ': expected ' + frame.url);
							}
						}

						// now go through all listeners and send the message
						// if they have a method defined to handle it
						for (var i in frame.listeners) {
							var l = frame.listeners[i]
							if (l && l[details.method]) {
								var method = l[details.method];
								method.apply(l, details.args);
							}
						}
					}
				}
			}
		}

		/**
		 * Enables a named frame for communication. This is used for all
		 * communication to this named item
		 *
		 * @param String name
		 *				The name can be either an iframe element name, or the
		 *				string 'parent' to indicate that it's the parent
		 *				of this frame that we're interested in
		 */
		var getFrame = function (name, proxyUrl) {
			if (!frames[name]) {
				// see whether or not the frame exists. If so, we need
				// to use its data-proxy attribute for getting the proxy
				// location
				var elem = $('iframe[name=' + name + ']');
				elem.load(function () {
					deferredSend(name);
				})
				if ((elem && elem.length) || (name == 'parent' && proxyUrl)) {
					var windowProxy = null;
					if (proxyUrl) {
						windowProxy = new Porthole.WindowProxy(proxyUrl);
					} else {
						proxyUrl = elem.attr('data-proxy');
						windowProxy = new Porthole.WindowProxy(proxyUrl, name);
					}

					windowProxy.addEventListener(messageHandler(name));
					frames[name] = {
						url: proxyUrl,
						channel: windowProxy,
						listeners: []
					};
				}
			}
			return frames[name];
		}

		/**
		 * Register to listen to a particular frame. Any messages sent will be
		 * forwarded to the listener
		 *
		 * @param String frameName
		 *				The name of the frame we're passing communication to.
		 *				If we're the CHILD framed page, pass through the URL to
		 *				the PARENT page's proxy.html page. From that point
		 *				onwards, you can refer to the 'parent' frame in calls
		 *				to 'send'
		 */
		var listenTo = function (frameName, object) {
			var frame = null;
			if (frameName.indexOf('://') > 0) {
				frame = getFrame('parent', frameName);
			} else {
				frame = getFrame(frameName);
			}

			if (frame) {
				frame.listeners.push(object);
			}
		}

		/**
		 * Some 'send' requests are done before the framed page loads completely. This will
		 * send those messages
		 */
		var deferredSend = function (frameName) {
			loadedFrames[frameName] = true;
			if (deferred[frameName]) {
				for (var i in deferred[frameName]) {
					var method = deferred[frameName][i].method;
					var args = deferred[frameName][i].args;
					args.unshift(method); args.unshift(frameName);
					console.log("calling deferred send");
					console.log(args);
					send.apply(this, args);
				}
			}
		}

		/**
		 * Send a method call down to a particular named frame
		 */
		var send = function (frameName, method) {
			var args = Array.prototype.slice.call(arguments, 2);
			if (!loadedFrames[frameName]) {
				var list = deferred[frameName] != null ? deferred[frameName] : [];
				list.push({
					method: method,
					args: args
				});
				deferred[frameName] = list;
				return;
			}

			var frame = getFrame(frameName);
			if (frame) {
				// send data through the proxy, after first packaging things up
				var data = {
					method: method,
					args: args
				}

				data = JSON.stringify(data);
				frame.channel.postMessage(data);
			}
		}
		return {
			listenTo: listenTo,
			send: send
		}
	})();
})(jQuery);
<|endoftext|>


<commit_before>import React from 'react';
import { flatMap, keyBy, map, sample, forEach, reject, filter } from 'lodash';
import { extractPostsFromSignups } from '../../helpers';

import InboxItem from '../InboxItem';
import ModalContainer from '../ModalContainer';
import HistoryModal from '../HistoryModal';
import PagingButtons from '../PagingButtons';
import PostFilter from '../PostFilter';
import StatusCounter from '../StatusCounter';
import { RestApiClient} from '@dosomething/gateway';

class CampaignSingle extends React.Component {
  constructor(props) {
    super(props);

    this.state = {};

    this.api = new RestApiClient;
    this.updateQuantity = this.updateQuantity.bind(this);
    this.showHistory = this.showHistory.bind(this);
    this.hideHistory = this.hideHistory.bind(this);
    this.filterPosts = this.filterPosts.bind(this);
  }

  componentDidMount() {
    this.getPostsByStatus('accepted', this.props.campaign.id);
  }

  // Filter posts based on status.
  filterPosts(status) {
    this.getPostsByStatus(status.toLowerCase(), this.props.campaign.id);
  }

  // Open the history modal of the given post
  // @TODO - Figure out how to share this logic between components so it
  // doesn't need to be duplicated between components.
  showHistory(postId, event) {
    event.preventDefault()

    this.setState({
      displayHistoryModal: true,
      historyModalId: postId,
    });
  }

  // Close the open history modal
  hideHistory(event) {
    if (event) {
      event.preventDefault();
    }

    this.setState({
      displayHistoryModal: false,
      historyModalId: null,
    });
  }

   // Update a signups quanity.
  updateQuantity(signup, newQuantity) {
    // Fields to send to /posts
    const fields = {
      northstar_id: signup.northstar_id,
      campaign_id: signup.campaign_id,
      campaign_run_id: signup.campaign_run_id,
      quantity: newQuantity,
    };

    // Make API request to Rogue to update the quantity on the backend
    let request = this.api.post('posts', fields);

    request.then((result) => {
      // Update the state
      this.setState((previousState) => {
        const newState = {...previousState};

        newState.signups[signup.id].quantity = result.quantity;

        return newState;
      });
    });

    // Close the modal
    this.hideHistory();
  }

  // Make API call to GET /posts to get posts by filtered status.
  getPostsByStatus(status, campaignId) {
    this.api = new RestApiClient;

    this.api.get('api/v2/posts', {
      filter: {
        status: status,
        campaign_id: campaignId,
      },
      include: 'signup,siblings',
    })
    .then(json => this.setState({
      posts: keyBy(json.data, 'id'),
      filter: status,
      postTotals: json.meta.pagination.total,
      displayHistoryModal: null,
      historyModalId: null,
      nextPage: json.meta.pagination.links.next,
      prevPage: json.meta.pagination.links.previous,
    }));
  }

  render() {
    const posts = this.state.posts;
    const campaign = this.props.campaign;

    return (

        <StatusCounter postTotals={this.props.post_totals} campaign={campaign} />

        <PostFilter onChange={this.filterPosts} />

        { map(posts, (post, key) => post.status === this.state.filter ? <InboxItem allowReview={false} onUpdate={this.updatePost} onTag={this.updateTag} showHistory={this.showHistory} deletePost={this.deletePost} key={key} post={post} campaign={campaign} signup={post.signup.data} /> : null) }

        <ModalContainer>
            {this.state.displayHistoryModal ? <HistoryModal id={this.state.historyModalId} onUpdate={this.updateQuantity} onClose={e => this.hideHistory(e)} details={{post: posts[this.state.historyModalId], campaign: campaign, signups: this.state.signups}}/> : null}
        </ModalContainer>

        <PagingButtons prev={this.state.prevPage} next={this.state.nextPage}></PagingButtons>

    )
  }
}

export default CampaignSingle;
<commit_msg>adds review buttons to campaign single page<commit_after>import React from 'react';
import { flatMap, keyBy, map, sample, forEach, reject, filter } from 'lodash';
import { RestApiClient} from '@dosomething/gateway';

import InboxItem from '../InboxItem';
import ModalContainer from '../ModalContainer';
import HistoryModal from '../HistoryModal';
import PagingButtons from '../PagingButtons';
import PostFilter from '../PostFilter';
import StatusCounter from '../StatusCounter';

class CampaignSingle extends React.Component {
  constructor(props) {
    super(props);

    this.state = {};

    this.api = new RestApiClient;
    this.updatePost = this.updatePost.bind(this);
    this.updateTag = this.updateTag.bind(this);
    this.updateQuantity = this.updateQuantity.bind(this);
    this.showHistory = this.showHistory.bind(this);
    this.hideHistory = this.hideHistory.bind(this);
    this.deletePost = this.deletePost.bind(this);
    this.filterPosts = this.filterPosts.bind(this);
  }

  componentDidMount() {
    this.getPostsByStatus('accepted', this.props.campaign.id);
  }

  // Filter posts based on status.
  filterPosts(status) {
    this.getPostsByStatus(status.toLowerCase(), this.props.campaign.id);
  }

  // Open the history modal of the given post
  // @TODO - Figure out how to share this logic between components so it
  // doesn't need to be duplicated between components.
  showHistory(postId, event) {
    event.preventDefault()

    this.setState({
      displayHistoryModal: true,
      historyModalId: postId,
    });
  }

  // Close the open history modal
  hideHistory(event) {
    if (event) {
      event.preventDefault();
    }

    this.setState({
      displayHistoryModal: false,
      historyModalId: null,
    });
  }

   // Update a signups quanity.
  updateQuantity(signup, newQuantity) {
    // Fields to send to /posts
    const fields = {
      northstar_id: signup.northstar_id,
      campaign_id: signup.campaign_id,
      campaign_run_id: signup.campaign_run_id,
      quantity: newQuantity,
    };

    // Make API request to Rogue to update the quantity on the backend
    let request = this.api.post('posts', fields);

    request.then((result) => {
      // Update the state
      this.setState((previousState) => {
        const newState = {...previousState};

        newState.signups[signup.id].quantity = result.quantity;

        return newState;
      });
    });

    // Close the modal
    this.hideHistory();
  }

    // Updates a post status.
  updatePost(postId, fields) {
    fields.post_id = postId;

    let request = this.api.put('reviews', fields);

    request.then((result) => {
      this.setState((previousState) => {
        const newState = {...previousState};

        newState.posts[postId].status = fields.status;

        return newState;
      });
    });

  }

  // Tag a post.
  updateTag(postId, tag) {
    const fields = {
      post_id: postId,
      tag_name: tag,
    };

    let response = this.api.post('tags', fields);
    response.then((result) => {
      this.setState((previousState) => {
        const newState = {...previousState};
        const user = newState.posts[postId].user;
        const signup = newState.posts[postId].signup.data;

        console.log(newState);
        newState.posts[postId] = result['data'];

        // Keep the user and signup from the initial page load.
        newState.posts[postId].user = user;
        // newState.posts[postId].signup.data = signup;

        return newState;
      });
    });
  }

  // Delete a post.
  deletePost(postId, event) {
    event.preventDefault();
    const confirmed = confirm('🚨🔥🚨Are you sure you want to delete this?🚨🔥🚨');

    if (confirmed) {
      // Make API request to Rogue to update the quantity on the backend
      let response = this.api.delete('posts/'.concat(postId));

      response.then((result) => {
        // Update the state
        this.setState((previousState) => {
          var newState = {...previousState};

          // Remove the deleted post from the state
          delete(newState.posts[postId]);

          // Return the new state
          return newState;
        });
      });
    }
  }

  // Make API call to GET /posts to get posts by filtered status.
  getPostsByStatus(status, campaignId) {
    this.api = new RestApiClient;

    this.api.get('api/v2/posts', {
      filter: {
        status: status,
        campaign_id: campaignId,
      },
      include: 'signup,siblings',
    })
    .then(json => this.setState({
      posts: keyBy(json.data, 'id'),
      filter: status,
      postTotals: json.meta.pagination.total,
      displayHistoryModal: null,
      historyModalId: null,
      nextPage: json.meta.pagination.links.next,
      prevPage: json.meta.pagination.links.previous,
    }));
  }

  render() {
    const posts = this.state.posts;
    const campaign = this.props.campaign;

    return (

        <StatusCounter postTotals={this.props.post_totals} campaign={campaign} />

        <PostFilter onChange={this.filterPosts} />

        { map(posts, (post, key) => post.status === this.state.filter ? <InboxItem allowReview={true} onUpdate={this.updatePost} onTag={this.updateTag} showHistory={this.showHistory} deletePost={this.deletePost} key={key} post={post} campaign={campaign} signup={post.signup.data} /> : null) }

        <ModalContainer>
            {this.state.displayHistoryModal ? <HistoryModal id={this.state.historyModalId} onUpdate={this.updateQuantity} onClose={e => this.hideHistory(e)} details={{post: posts[this.state.historyModalId], campaign: campaign, signups: this.state.signups}}/> : null}
        </ModalContainer>

        <PagingButtons prev={this.state.prevPage} next={this.state.nextPage}></PagingButtons>

    )
  }
}

export default CampaignSingle;
<|endoftext|>


<commit_before>// CSS 默认样式
const printTemplateDefaultStyle = `
* {
  margin: 0;
  padding: 0;
}

html, body {
  font-size: 12pt;
  color: #000;
}

table {
  width: 100%;
  border-collapse: collapse;
  caption-side: top;
  empty-cells: show;
}

table th,
table td {
  padding: 8pt 0;
  text-align: left;
}

caption {
  width: 100%;
  padding: 8pt 0;
  text-align: left;
  border: none;
  border-bottom: 1pt solid #000;
}

@media screen {
  section {
    border: 1pt dashed #666;
    padding: 14mm 12mm 10mm;
  }
}

@media print {
  @page A4 {
    size: 210mm 290mm;
    margin: 20mm 15mm;
  }

  section {
    page: A4;
    page-break-after: always;
  }
}

article.print-wrapper {
  margin: 20pt auto;
}
`

// CSS工具样式
const getTemplateToolStyle = `
.text-left {
  text-align: left;
}

.text-center {
  text-align: center;
}

.text-right {
  text-align: right;
}

.text-middle {
  vertical-align: middle;
}

.text-baseline {
  vertical-align: baseline;
}

.font-bold {
  font-weight: bold;
}

.font-normal {
  font-weight: normal;
}

.border {
  border: 1pt solid #000;
}

.border-top {
  border-top: 1pt solid #000;
}

.border-right {
  border-right: 1pt solid #000;
}

.border-bottom {
  border-bottom: 1pt solid #000;
}

.border-left {
  border-left: 1pt solid #000;
}

.no-border {
  border: none;
}

.clearfix::after {
  content: '';
  clear: both;
  display: block;
  height: 0;
  font-size: 0;
  display: block;
}

.clearfix {
  zoom: 1;
}

.fl {
  float: left;
}

.fr {
  float: right;
}
`

/**
 * 创建模板
 * @param {string} body
 * @param {object} options
 *
 * 注：
 * 1. section是关键标签，用来表示A4纸张，请谨慎使用
 * 2. table，caption都做过处理，可以直接使用而不去设置额外样式
 * 3. 请使用pt作为单位，因为打印设备真正识别的单位为pt
 */
function getPrintTemplate (body = '', options = {}) {
  return `
    <!DOCTYPE html>

        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <meta name="robots" content="none">
        <meta name="renderer" content="webkit">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
        <meta http-equiv="cache-control" content="no-cache">
        <meta http-equiv="Cache-Control" content="no-siteapp" />
        <meta http-equiv="expires" content="Tue Jan 07 2020 11:19:57 GMT+0800" />
        <title>${options.title}</title>

            ${body}

  `
}

function getCommonHandles (opener) {
  return {
    insertStyle (style) {
      if (style) {
        const $styleElem = document.createElement('style')
        $styleElem.innerText = style
        opener.document.head.appendChild($styleElem)
      }
    },
    close () {
      if (!opener.closed) {
        opener.close()
      }
    },
    print () {
      if (!opener.closed) {
        opener.print()
      }
    }
  }
}

const handlers = {
  createNewPrintWindow () {
    // 打印页窗口句柄
    const opener = window.open('', '_blank')
    // 提示等待信息
    opener.document.body.innerHTML =
      '
正在调取打印数据，请稍等...
'

    opener.$handle = getCommonHandles(opener)

    return opener
  },

  createNewPrintFrame () {
    // 打印页iframe
    const $iframe = document.createElement('iframe')
    // 设置iframe的链接
    $iframe.setAttribute('src', 'about:blank')
    // 调整frame样式
    $iframe.setAttribute(
      'style',
      'position: absolute; left: -100000px; top: -10000px; z-index: 0; width: 200px; height: 200px;'
    )
    // 将iframe插入到document中
    document.body.appendChild($iframe)
    // 获取窗口句柄
    const opener = $iframe.contentWindow
    opener.$handle = Object.assign(getCommonHandles(opener), {
      close () {
        document.body.removeChild($iframe)
      }
    })

    return opener
  }
}

const handlerMinor = {
  win: 'window',
  frame: 'iframe'
}

class AsyncPrint {
  constructor (options = {}) {
    this._options = Object.assign(
      { title: '打印页', width: '210mm', delay: 50, handler: handlerMinor.frame },
      options
    )

    if (this._options.debug) {
      this._options = Object.assign(this._options, { handler: handlerMinor.win })
    }

    const handlerType = this._options.handler

    switch (handlerType) {
      case handlerMinor.win:
        this.opener = handlers.createNewPrintWindow()
        break
      case handlerMinor.frame:
        this.opener = handlers.createNewPrintFrame()
        break
      default:
        throw new Error(`未知句柄类型： ${handlerType}`)
    }
  }

  exec (body = '') {
    const self = this
    const { opener, _options: options } = self

    if (body) {
      options.body = body
    }

    if (!options.body) {
      self.close()
      return false
    }

    // 生成HTML字符串模板
    const html = getPrintTemplate(options.body, options)
    // 将字符串模板渲染到新窗口中
    opener.document.write(html)

    // 触发打印前的回调函数
    opener.onbeforeprint = function () {
      opener.focus()
      if (typeof options.beforeprint === 'function') {
        options.beforeprint(opener)
      }
    }

    // 触发打印后的回调函数（默认是关闭新窗口）
    opener.onafterprint = function () {
      let autoClose = true

      if (typeof opener.afterprint === 'function') {
        autoClose = options.afterprint(opener)
      }

      // 判断是否自动关闭
      if (autoClose !== false) {
        self.close()
      }
    }

    // 插入额外的样式
    opener.$handle.insertStyle(options.extraCSS)

    if (!options.debug) {
      // 延迟调用打印接口
      setTimeout(opener.$handle.print, options.delay)
    }

    return true
  }

  /**
   * 关闭打印页面
   */
  close () {
    if (!this.opener.closed) {
      this.opener.$handle.close()
    }
  }
}

/**
 * 同步打印工具方法
 * @param {object} options 打印配置项
 * @property {string} title 标签标题(默认是打印页)
 * @property {string} body 打印内容
 * @property {string} extraCSS 额外的样式信息
 * @property {string} width 内容宽度（默认是210mm）
 * @property {string} delay 延迟打印（默认是50ms）
 * @property {string} handler 打印处理模式（默认是iframe）
 * @property {boolean} debug 调试模式（默认是false）
 * @property {function} beforeprint 打印前调用的函数 参数 opener 窗口对象
 * @property {function} afterprint 打印后调用的函数 参数 opener 窗口对象
 * @returns {AsyncPrint} AsyncPrint实例
 * @function exec 执行打印操作
 * @param {string} body 打印的内容
 * @function close 关闭打印窗口
 *
 * 注：打印的公共样式可以查看 getTemplateToolStyle 变量
 */
export default function print (options = {}) {
  return new AsyncPrint(options)
}
<commit_msg>fix: 支持同步打印<commit_after>// CSS 默认样式
const printTemplateDefaultStyle = `
* {
  margin: 0;
  padding: 0;
}

html, body {
  font-size: 12pt;
  color: #000;
}

table {
  width: 100%;
  border-collapse: collapse;
  caption-side: top;
  empty-cells: show;
}

table th,
table td {
  padding: 8pt 0;
  text-align: left;
}

caption {
  width: 100%;
  padding: 8pt 0;
  text-align: left;
  border: none;
  border-bottom: 1pt solid #000;
}

@media screen {
  section {
    border: 1pt dashed #666;
    padding: 14mm 12mm 10mm;
  }
}

@media print {
  @page A4 {
    size: 210mm 290mm;
    margin: 20mm 15mm;
  }

  section {
    page: A4;
    page-break-after: always;
  }
}

article.print-wrapper {
  margin: 20pt auto;
}
`

// CSS工具样式
const getTemplateToolStyle = `
.text-left {
  text-align: left;
}

.text-center {
  text-align: center;
}

.text-right {
  text-align: right;
}

.text-middle {
  vertical-align: middle;
}

.text-baseline {
  vertical-align: baseline;
}

.font-bold {
  font-weight: bold;
}

.font-normal {
  font-weight: normal;
}

.border {
  border: 1pt solid #000;
}

.border-top {
  border-top: 1pt solid #000;
}

.border-right {
  border-right: 1pt solid #000;
}

.border-bottom {
  border-bottom: 1pt solid #000;
}

.border-left {
  border-left: 1pt solid #000;
}

.no-border {
  border: none;
}

.clearfix::after {
  content: '';
  clear: both;
  display: block;
  height: 0;
  font-size: 0;
  display: block;
}

.clearfix {
  zoom: 1;
}

.fl {
  float: left;
}

.fr {
  float: right;
}
`

/**
 * 创建模板
 * @param {string} body
 * @param {object} options
 *
 * 注：
 * 1. section是关键标签，用来表示A4纸张，请谨慎使用
 * 2. table，caption都做过处理，可以直接使用而不去设置额外样式
 * 3. 请使用pt作为单位，因为打印设备真正识别的单位为pt
 */
function getPrintTemplate (body = '', options = {}) {
  return `
    <!DOCTYPE html>

        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <meta name="robots" content="none">
        <meta name="renderer" content="webkit">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
        <meta http-equiv="cache-control" content="no-cache">
        <meta http-equiv="Cache-Control" content="no-siteapp" />
        <meta http-equiv="expires" content="Tue Jan 07 2020 11:19:57 GMT+0800" />
        <title>${options.title}</title>

            ${body}

  `
}

function getCommonHandles (opener) {
  return {
    insertStyle (cssText) {
      if (cssText) {
        const $styleElem = document.createElement('style')
        if ($styleElem.styleSheet) {
          // IE
          $styleElem.type = 'text/css'
          const func = function () {
            // 防止IE中stylesheet数量超过限制而发生错误
            $styleElem.styleSheet.cssText = cssText
          }
          // 如果当前styleSheet还不能用，则放到异步中则行
          if ($styleElem.styleSheet.disabled) {
            setTimeout(func, 0)
          } else {
            func()
          }
        } else {
          // w3c浏览器中只要创建文本节点插入到style元素中就行了
          const $textNode = document.createTextNode(cssText)
          $styleElem.appendChild($textNode)
        }
        opener.document.head.appendChild($styleElem)
      }
    },
    close () {
      if (!opener.closed) {
        opener.close()
      }
    },
    print () {
      if (!opener.closed) {
        opener.print()
      }
    }
  }
}

const handlers = {
  createNewPrintWindow () {
    // 打印页窗口句柄
    const opener = window.open('', '_blank')
    // 提示等待信息
    opener.document.body.innerHTML =
      '
正在调取打印数据，请稍等...
'

    opener.$handle = getCommonHandles(opener)

    return opener
  },

  createNewPrintFrame () {
    // 打印页iframe
    const $iframe = document.createElement('iframe')
    // 设置iframe的链接
    $iframe.setAttribute('src', 'about:blank')
    // 调整frame样式
    $iframe.setAttribute(
      'style',
      'position: absolute; left: -100000px; top: -10000px; z-index: 0; width: 200px; height: 200px;'
    )
    // 将iframe插入到document中
    document.body.appendChild($iframe)
    // 获取窗口句柄
    const opener = $iframe.contentWindow
    opener.$handle = Object.assign(getCommonHandles(opener), {
      close () {
        document.body.removeChild($iframe)
      }
    })

    return opener
  }
}

const handlerMinor = {
  win: 'window',
  frame: 'iframe'
}

class AsyncPrint {
  constructor (options = {}) {
    this._options = Object.assign(
      { title: '打印页', width: '210mm', delay: 50, handler: handlerMinor.frame },
      options
    )

    if (this._options.debug) {
      this._options = Object.assign(this._options, { handler: handlerMinor.win })
    }

    const handlerType = this._options.handler

    switch (handlerType) {
      case handlerMinor.win:
        this.opener = handlers.createNewPrintWindow()
        break
      case handlerMinor.frame:
        this.opener = handlers.createNewPrintFrame()
        break
      default:
        throw new Error(`未知句柄类型： ${handlerType}`)
    }

    if (this._options.body) {
      this.exec()
    }
  }

  exec (body = '') {
    const self = this
    const { opener, _options: options } = self

    if (body) {
      options.body = body
    }

    if (!options.body) {
      self.close()
      return false
    }

    // 生成HTML字符串模板
    const html = getPrintTemplate(options.body, options)
    // 将字符串模板渲染到新窗口中
    opener.document.write(html)

    // 触发打印前的回调函数
    opener.onbeforeprint = function () {
      opener.focus()
      if (typeof options.beforeprint === 'function') {
        options.beforeprint(opener)
      }
    }

    // 触发打印后的回调函数（默认是关闭新窗口）
    opener.onafterprint = function () {
      let autoClose = true

      if (typeof opener.afterprint === 'function') {
        autoClose = options.afterprint(opener)
      }

      // 判断是否自动关闭
      if (autoClose !== false) {
        self.close()
      }
    }

    // 插入额外的样式
    opener.$handle.insertStyle(options.extraCSS)

    if (!options.debug) {
      // 延迟调用打印接口
      setTimeout(opener.$handle.print, options.delay)
    }

    return true
  }

  /**
   * 关闭打印页面
   */
  close () {
    if (!this.opener.closed) {
      this.opener.$handle.close()
    }
  }
}

/**
 * 同步打印工具方法
 * @param {object} options 打印配置项
 * @property {string} title 标签标题(默认是打印页)
 * @property {string} body 打印内容
 * @property {string} extraCSS 额外的样式信息
 * @property {string} width 内容宽度（默认是210mm）
 * @property {string} delay 延迟打印（默认是50ms）
 * @property {string} handler 打印处理模式（默认是iframe）
 * @property {boolean} debug 调试模式（默认是false）
 * @property {function} beforeprint 打印前调用的函数 参数 opener 窗口对象
 * @property {function} afterprint 打印后调用的函数 参数 opener 窗口对象
 * @returns {AsyncPrint} AsyncPrint实例
 * @function exec 执行打印操作
 * @param {string} body 打印的内容
 * @function close 关闭打印窗口
 *
 * * 注：
 * 1. section是关键标签，用来表示A4纸张，请谨慎使用
 * 2. table，caption都做过处理，可以直接使用而不去设置额外样式
 * 3. 请使用pt作为单位，因为打印设备真正识别的单位为pt
 * 4. 打印的公共样式可以查看 getTemplateToolStyle 变量
 */
export default function print (options = {}) {
  return new AsyncPrint(options)
}
<|endoftext|>