<commit_before>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `getInput()`.
    return /* Your Expression */ move||getInput();
}

function getComputerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `randomPlay()`.
    return /* Your Expression */move||randomPlay();
}

function getWinner(playerMove,computerMove) {
    var winner;
    // Write code that will set winner to either 'player', 'computer', or 'tie' based on the values of playerMove and computerMove.
    // Assume that the only values playerMove and computerMove can have are 'rock', 'paper', and 'scissors'.
    // The rules of the game are that 'rock' beats 'scissors', 'scissors' beats 'paper', and 'paper' beats 'rock'.
    /* YOUR CODE HERE */
    if(playerMove == computerMove){
        winner = "tie";
    }
    else if((playerMove =="rock" && computerMove =="scissors" )||(playerMove =="scissors" && computerMove =="paper" )||(playerMove =="paper" && computerMove =="paper" )){
        winner = "player";
    }
    else if((computerMove =="rock" && playerMove =="scissors" )||(computerMove =="scissors" && playerMove =="paper" )||(computerMove =="paper" && playerMove =="paper" )){
        winner = "computer";
    }

    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    var winner;
    while (playerWins <= 5 || computerWins <=5){
        winner = getWinner(getPlayerMove(), getComputerMove());
        if (winner = "player"){ playerWins++;}
        else if(winner = "computer"){computerWins++;}
    }
    // Write code that plays 'Rock, Paper, Scissors' until either the player or the computer has won five times.
    /* YOUR CODE HERE */
    return [playerWins, computerWins];
}

<commit_msg>got rid of use strict<commit_after>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
//'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `getInput()`.
    return /* Your Expression */ move||getInput();
}

function getComputerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `randomPlay()`.
    return /* Your Expression */move||randomPlay();
}

function getWinner(playerMove,computerMove) {
    var winner;
    // Write code that will set winner to either 'player', 'computer', or 'tie' based on the values of playerMove and computerMove.
    // Assume that the only values playerMove and computerMove can have are 'rock', 'paper', and 'scissors'.
    // The rules of the game are that 'rock' beats 'scissors', 'scissors' beats 'paper', and 'paper' beats 'rock'.
    /* YOUR CODE HERE */
    if(playerMove == computerMove){
        winner = "tie";
    }
    else if((playerMove =="rock" && computerMove =="scissors" )||(playerMove =="scissors" && computerMove =="paper" )||(playerMove =="paper" && computerMove =="paper" )){
        winner = "player";
    }
    else if((computerMove =="rock" && playerMove =="scissors" )||(computerMove =="scissors" && playerMove =="paper" )||(computerMove =="paper" && playerMove =="paper" )){
        winner = "computer";
    }

    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    var winner;
    while (playerWins <= 5 || computerWins <=5){
        winner = getWinner(getPlayerMove(), getComputerMove());
        if (winner = "player"){ playerWins++;}
        else if(winner = "computer"){computerWins++;}
    }
    // Write code that plays 'Rock, Paper, Scissors' until either the player or the computer has won five times.
    /* YOUR CODE HERE */
    return [playerWins, computerWins];
}

<|endoftext|>


<commit_before>/*eslint-env browser */
/*globals require: false, module: false */
'use strict';

var React = require('react');
var _ = require('./underscore_ext');
var MenuItem = require('react-bootstrap/lib/MenuItem');
var SplitButton = require('react-bootstrap/lib/SplitButton');
var Resizable = require('react-resizable').Resizable;
var DefaultTextInput = require('./DefaultTextInput');
var {RefGeneAnnotation} = require('./refGeneExons');
var xenaQuery = require('./xenaQuery');

// XXX move this?
function download([fields, rows]) {
	var txt = _.map([fields].concat(rows), row => row.join('\t')).join('\n');
	// use blob for bug in chrome: https://code.google.com/p/chromium/issues/detail?id=373182
	var url = URL.createObjectURL(new Blob([txt], { type: 'text/tsv' }));
	var a = document.createElement('a');
	var filename = 'xenaDownload.tsv';
	_.extend(a, { id: filename, download: filename, href: url });
	document.body.appendChild(a);
	a.click();
	document.body.removeChild(a);
}

// For geneProbesMatrix we will average across probes to compute KM. For
// other types, we can't support multiple fields. This should really be
// in a prop set by the column, or in a multimethod. Having this here is bad.

function disableKM(column, hasSurvival) {
	if (!hasSurvival) {
		return [true, 'No survival data for cohort'];
	}
	if (column.fields.length > 1 && column.dataType !== 'geneProbesMatrix') {
		return [true, 'Unsupported for multiple genes/ids'];
	}
	return [false, ''];
}

var ResizeOverlay = React.createClass({
	getInitialState: () => ({zooming: false}),
	onResizeStart: function () {
		var {width, height} = this.props;
		this.setState({zooming: true, zoomSize: {width, height}});
	},
	onResize: function (ev, {size}) {
		this.setState({zoomSize: size});
	},
	onResizeStop: function (...args) {
		var {onResizeStop} = this.props;
		this.setState({zooming: false});
		if (onResizeStop) {
			onResizeStop(...args);
		}
	},
	render: function () {
		var {zooming, zoomSize} = this.state;
		// XXX This margin setting really belongs elsewhere.
		return (

				{zooming ?
 : null}
				<Resizable handleSize={[20, 20]}
					onResizeStop={this.onResizeStop}
					onResize={this.onResize}
					onResizeStart={this.onResizeStart}
					width={this.props.width}
					height={this.props.height}>

						{this.props.children}

				</Resizable>

		);
	}
});

var Column = React.createClass({
	onResizeStop: function (ev, {size}) {
		this.props.callback(['resize', this.props.id, size]);
	},
	onRemove: function () {
		this.props.callback(['remove', this.props.id]);
	},
	onDownload: function () {
		download(this.props.download());
	},
	onAbout: function () {
		var {column} = this.props;
		var dsID = column.dsID;
		var [host, dataset] = xenaQuery.parse_host(dsID);
		var url = `../datapages/?dataset=${encodeURIComponent(dataset)}&host=${encodeURIComponent(host)}`;
		window.open(url);
	},
	onViz: function () {
		this.props.onViz(this.props.id);
	},
	onKm: function () {
		let {callback, id} = this.props;
		callback(['km-open', id]);
	},
	render: function () {
		var {id, callback, plot, legend, column, zoom, menu, data, hasSurvival} = this.props,
			{width, columnLabel, fieldLabel} = column,
			[kmDisabled, kmTitle] = disableKM(column, hasSurvival),
			// move this to state to generalize to other annotations.
			doRefGene = column.dataType === 'mutationVector',
			// In FF spans don't appear as event targets. In Chrome, they do.
			// If we omit Sortable-handle here, Chrome will only catch events
			// in the button but not in the span. If we omit Sortable-handle
			// in SplitButton, FF will catch no events, since span doesn't
			// emit any.
			moveIcon = (
			);

		return (

				<SplitButton className='Sortable-handle' title={moveIcon} bsSize='xsmall'>
					{menu}
					{menu && <MenuItem divider />}
					<MenuItem title={kmTitle} onSelect={this.onKm} disabled={kmDisabled}>Kaplan Meier Plot</MenuItem>
					<MenuItem onSelect={this.onDownload}>Download</MenuItem>
					<MenuItem onSelect={this.onAbout}>About the Dataset</MenuItem>
					<MenuItem onSelect={this.onViz}>Viz Settings</MenuItem>
					<MenuItem onSelect={this.onRemove}>Remove</MenuItem>
				</SplitButton>

				<DefaultTextInput
					columnID={id}
					callback={callback}
					eventName='columnLabel'
					value={columnLabel} />
				<DefaultTextInput
					columnID={id}
					callback={callback}
					eventName='fieldLabel'
					value={fieldLabel} />

					{doRefGene && data ?
						<RefGeneAnnotation
							width={width}
							refGene={data.refGene[column.fields[0]]}
							layout={column.layout}
							position={{gene: column.fields[0]}}/> : null}

				<ResizeOverlay
					onResizeStop={this.onResizeStop}
					width={width}
					height={zoom.height}>

						{plot}
				</ResizeOverlay>
				{legend}

		);
	}
});

module.exports = Column;
<commit_msg>Drop unnecessary dataType check.<commit_after>/*eslint-env browser */
/*globals require: false, module: false */
'use strict';

var React = require('react');
var _ = require('./underscore_ext');
var MenuItem = require('react-bootstrap/lib/MenuItem');
var SplitButton = require('react-bootstrap/lib/SplitButton');
var Resizable = require('react-resizable').Resizable;
var DefaultTextInput = require('./DefaultTextInput');
var {RefGeneAnnotation} = require('./refGeneExons');
var xenaQuery = require('./xenaQuery');

// XXX move this?
function download([fields, rows]) {
	var txt = _.map([fields].concat(rows), row => row.join('\t')).join('\n');
	// use blob for bug in chrome: https://code.google.com/p/chromium/issues/detail?id=373182
	var url = URL.createObjectURL(new Blob([txt], { type: 'text/tsv' }));
	var a = document.createElement('a');
	var filename = 'xenaDownload.tsv';
	_.extend(a, { id: filename, download: filename, href: url });
	document.body.appendChild(a);
	a.click();
	document.body.removeChild(a);
}

// For geneProbesMatrix we will average across probes to compute KM. For
// other types, we can't support multiple fields. This should really be
// in a prop set by the column, or in a multimethod. Having this here is bad.

function disableKM(column, hasSurvival) {
	if (!hasSurvival) {
		return [true, 'No survival data for cohort'];
	}
	if (column.fields.length > 1) {
		return [true, 'Unsupported for multiple genes/ids'];
	}
	return [false, ''];
}

var ResizeOverlay = React.createClass({
	getInitialState: () => ({zooming: false}),
	onResizeStart: function () {
		var {width, height} = this.props;
		this.setState({zooming: true, zoomSize: {width, height}});
	},
	onResize: function (ev, {size}) {
		this.setState({zoomSize: size});
	},
	onResizeStop: function (...args) {
		var {onResizeStop} = this.props;
		this.setState({zooming: false});
		if (onResizeStop) {
			onResizeStop(...args);
		}
	},
	render: function () {
		var {zooming, zoomSize} = this.state;
		// XXX This margin setting really belongs elsewhere.
		return (

				{zooming ?
 : null}
				<Resizable handleSize={[20, 20]}
					onResizeStop={this.onResizeStop}
					onResize={this.onResize}
					onResizeStart={this.onResizeStart}
					width={this.props.width}
					height={this.props.height}>

						{this.props.children}

				</Resizable>

		);
	}
});

var Column = React.createClass({
	onResizeStop: function (ev, {size}) {
		this.props.callback(['resize', this.props.id, size]);
	},
	onRemove: function () {
		this.props.callback(['remove', this.props.id]);
	},
	onDownload: function () {
		download(this.props.download());
	},
	onAbout: function () {
		var {column} = this.props;
		var dsID = column.dsID;
		var [host, dataset] = xenaQuery.parse_host(dsID);
		var url = `../datapages/?dataset=${encodeURIComponent(dataset)}&host=${encodeURIComponent(host)}`;
		window.open(url);
	},
	onViz: function () {
		this.props.onViz(this.props.id);
	},
	onKm: function () {
		let {callback, id} = this.props;
		callback(['km-open', id]);
	},
	render: function () {
		var {id, callback, plot, legend, column, zoom, menu, data, hasSurvival} = this.props,
			{width, columnLabel, fieldLabel} = column,
			[kmDisabled, kmTitle] = disableKM(column, hasSurvival),
			// move this to state to generalize to other annotations.
			doRefGene = column.dataType === 'mutationVector',
			// In FF spans don't appear as event targets. In Chrome, they do.
			// If we omit Sortable-handle here, Chrome will only catch events
			// in the button but not in the span. If we omit Sortable-handle
			// in SplitButton, FF will catch no events, since span doesn't
			// emit any.
			moveIcon = (
			);

		return (

				<SplitButton className='Sortable-handle' title={moveIcon} bsSize='xsmall'>
					{menu}
					{menu && <MenuItem divider />}
					<MenuItem title={kmTitle} onSelect={this.onKm} disabled={kmDisabled}>Kaplan Meier Plot</MenuItem>
					<MenuItem onSelect={this.onDownload}>Download</MenuItem>
					<MenuItem onSelect={this.onAbout}>About the Dataset</MenuItem>
					<MenuItem onSelect={this.onViz}>Viz Settings</MenuItem>
					<MenuItem onSelect={this.onRemove}>Remove</MenuItem>
				</SplitButton>

				<DefaultTextInput
					columnID={id}
					callback={callback}
					eventName='columnLabel'
					value={columnLabel} />
				<DefaultTextInput
					columnID={id}
					callback={callback}
					eventName='fieldLabel'
					value={fieldLabel} />

					{doRefGene && data ?
						<RefGeneAnnotation
							width={width}
							refGene={data.refGene[column.fields[0]]}
							layout={column.layout}
							position={{gene: column.fields[0]}}/> : null}

				<ResizeOverlay
					onResizeStop={this.onResizeStop}
					width={width}
					height={zoom.height}>

						{plot}
				</ResizeOverlay>
				{legend}

		);
	}
});

module.exports = Column;
<|endoftext|>


<commit_before>var gulp = require('gulp'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    del = require('del'),
    browserify = require('browserify'),
    source = require('vinyl-source-stream'),
    buffer = require('vinyl-buffer'),
    uglify = require('gulp-uglify'),
    sourcemaps = require('gulp-sourcemaps'),
    babelify = require('babelify'),
    inline = require('gulp-inline-source'),
    fs = require('fs'),
    path = require('path');

gulp.task('default', function(done) {
  gulp.series(['styles', 'scripts', 'html'], 'inline')();
  done();
});

gulp.task('watch', gulp.series('default', function() {
  gulp.watch(['frontend/src/**/*'], ['default']);
}));

gulp.task('styles', function() {
  return gulp.src('frontend/src/styles/**/*.css')
    .pipe(concat('style.css'))
    .pipe(gulp.dest('frontend/dist/styles'));
});

gulp.task('scripts', function() {

  var b = browserify({
    entries: 'frontend/src/scripts/main.js',
    debug: true
  });

  return b
    .transform('babelify', {presets: ["env"]})
    .bundle()
    .pipe(source('main.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadmaps: true}))
      .pipe(uglify())
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./frontend/dist/scripts'));

});

gulp.task('html', function() {
  return gulp.src('frontend/src/index.html')
    .pipe(gulp.dest('dist'));
});

gulp.task('inline', function(done) {
  htmlpath = path.resolve('frontend/dist/index.html');
  inline(htmlpath, {
    compress: true,
    rootpath: path.resolve('dist'),
  }, function(err, html){
    fs.writeFileSync('frontend/dist/index.html', html);
  });
  done();
});
<commit_msg>gulpfile.js: Fixed output path for index.html<commit_after>var gulp = require('gulp'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    del = require('del'),
    browserify = require('browserify'),
    source = require('vinyl-source-stream'),
    buffer = require('vinyl-buffer'),
    uglify = require('gulp-uglify'),
    sourcemaps = require('gulp-sourcemaps'),
    babelify = require('babelify'),
    inline = require('gulp-inline-source'),
    fs = require('fs'),
    path = require('path');

gulp.task('default', function(done) {
  gulp.series(['styles', 'scripts', 'html'], 'inline')();
  done();
});

gulp.task('watch', gulp.series('default', function() {
  gulp.watch(['frontend/src/**/*'], ['default']);
}));

gulp.task('styles', function() {
  return gulp.src('frontend/src/styles/**/*.css')
    .pipe(concat('style.css'))
    .pipe(gulp.dest('frontend/dist/styles'));
});

gulp.task('scripts', function() {

  var b = browserify({
    entries: 'frontend/src/scripts/main.js',
    debug: true
  });

  return b
    .transform('babelify', {presets: ["env"]})
    .bundle()
    .pipe(source('main.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadmaps: true}))
      .pipe(uglify())
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./frontend/dist/scripts'));

});

gulp.task('html', function() {
  return gulp.src('frontend/src/index.html')
    .pipe(gulp.dest('./frontend/dist'));
});

gulp.task('inline', function(done) {
  htmlpath = path.resolve('frontend/dist/index.html');
  inline(htmlpath, {
    compress: true,
    rootpath: path.resolve('dist'),
  }, function(err, html){
    fs.writeFileSync('frontend/dist/index.html', html);
  });
  done();
});
<|endoftext|>


<commit_before>sf.scores = (function() {
    var maximumNumberOfScores = 10;
    var scores = [];

    var serverSendScore = function(newScore) {
        $.ajax({
            type: "POST",
            url: sf.setup.scoreApiUrl + 'add/',
            data: newScore
        });
    };

    var serverFetchScore = function() {
        $.getJSON(sf.setup.scoreApiUrl + '?maxValues=' + maximumNumberOfScores, function(data) {
            /*var newscores = [];
            for (var score in data) {
                newscores.push({name:data.name, levels:parseInt(data.levels), time:parseInt(data.time)});
            }*/
            scores = data.sort(scoreComparator).slice(0, maximumNumberOfScores);
        });
    }

    var scoreComparator = function(a, b) {
        //var rval = 0;
        if (parseInt(a.levels) > parseInt(b.levels)) {
            return -11;
        } else if (parseInt(a.levels) < parseInt(b.levels)) {
            return 1;
        } else if( parseInt(a.time) < parseInt(b.time) ) {
            return -1;
        } else if (parseInt(a.time) > parseInt(b.time)) {
            return 1;
        } else {
            return 0;
        }
        //console.log(rval, a, b);
    }

    var checkScore = function(levels, time) {
        var makesIt = false;
        if (scores.length < maximumNumberOfScores) {
            makesIt = true;
        }
        var newscore = {levels: levels, time:time};

        for (var i=0;i<scores.length && i < maximumNumberOfScores;i++) {
            if (scoreComparator(newscore, scores[i]) < 0) {
                makesIt = true;
            }
        }
        console.log('makesIt: ', makesIt);
        return makesIt;
    };

    var addScore = function(name, levels, time) {
        var newScore = {name: name, levels: levels, time:time};
        scores.push(newScore);
        scores.sort(scoreComparator);
        scores = scores.slice(0, maximumNumberOfScores);
        serverSendScore(newScore);
    };

    var getScores = function() {
        serverFetchScore();
        scores.sort(scoreComparator);
        sf.debug(scores);
        return scores;
    };

    return {
        checkScore: checkScore,
        addScore: addScore,
        scores: getScores,
        syncServer: serverFetchScore
    }
})();
<commit_msg>cosmetics for js/scores.js<commit_after>sf.scores = (function() {
    var maximumNumberOfScores = 10;
    var scores = [];

    var serverSendScore = function(newScore) {
        $.ajax({
            type: "POST",
            url: sf.setup.scoreApiUrl + 'add/',
            data: newScore
        });
    };

    var serverFetchScore = function() {
        $.getJSON(sf.setup.scoreApiUrl + '?maxValues=' + maximumNumberOfScores, function(data) {
            scores = data.sort(scoreComparator).slice(0, maximumNumberOfScores);
        });
    }

    var scoreComparator = function(a, b) {
        if (parseInt(a.levels) > parseInt(b.levels)) {
            return -1;
        } else if (parseInt(a.levels) < parseInt(b.levels)) {
            return 1;
        } else if( parseInt(a.time) < parseInt(b.time) ) {
            return -1;
        } else if (parseInt(a.time) > parseInt(b.time)) {
            return 1;
        } else {
            return 0;
        }
    }

    var checkScore = function(levels, time) {
        var makesIt = false;
        if (scores.length < maximumNumberOfScores) {
            makesIt = true;
        }
        var newscore = {levels: levels, time:time};

        for (var i=0;i<scores.length && i < maximumNumberOfScores;i++) {
            if (scoreComparator(newscore, scores[i]) < 0) {
                makesIt = true;
            }
        }
        console.log('makesIt: ', makesIt);
        return makesIt;
    };

    var addScore = function(name, levels, time) {
        var newScore = {name: name, levels: levels, time:time};
        scores.push(newScore);
        scores.sort(scoreComparator);
        scores = scores.slice(0, maximumNumberOfScores);
        serverSendScore(newScore);
    };

    var getScores = function() {
        serverFetchScore();
        scores.sort(scoreComparator);
        sf.debug(scores);
        return scores;
    };

    return {
        checkScore: checkScore,
        addScore: addScore,
        scores: getScores,
        syncServer: serverFetchScore
    }
})();
<|endoftext|>


<commit_before>import h from 'hyperscript';

import Page from '../Page';
import errorView from './error';

const MODE_FLIP = 'interactive';
const MODE_PREVIEW = 'grid';
const MODE_SHEET = 'print';
const MODE_OUTLINE = 'outline';

const ARRANGE_ONE = 'arrange_one';
const ARRANGE_SPREAD = 'arrange_two';
const ARRANGE_BOOKLET = 'arrange_booklet';
// const ARRANGE_SIGNATURE = 'arrange_signature';

const cropMarksSingle = () => h('.bindery-crop-wrap',
  h('.bindery-crop-top'),
  h('.bindery-crop-bottom'),
  h('.bindery-crop-left'),
  h('.bindery-crop-right'),
);
const cropMarksSpread = () => h('.bindery-crop-wrap',
  h('.bindery-crop-top'),
  h('.bindery-crop-bottom'),
  h('.bindery-crop-left'),
  h('.bindery-crop-right'),
  h('.bindery-crop-fold'),
);

const spread = function (...arg) {
  return h('.bindery-spread-wrapper', ...arg);
};

const bookletMeta = (i, len) => {
  const isFront = i % 4 === 0;
  const sheetIndex = parseInt((i + 1) / 4, 10) + 1;
  return h('.bindery-print-meta',
    `Sheet ${sheetIndex} of ${len / 4}: ${isFront ? 'Outside' : 'Inside'}`);
};

const ORIENTATION_STYLE_ID = 'bindery-orientation-stylesheet';

const setOrientationCSS = (newValue) => {
  let sheet;
  const existing = document.querySelector(`#${ORIENTATION_STYLE_ID}`);
  if (existing) {
    sheet = existing;
  } else {
    sheet = document.createElement('style');
    sheet.id = ORIENTATION_STYLE_ID;
  }
  sheet.innerHTML = `@page { size: ${newValue}; }`;
  document.head.appendChild(sheet);
};

const orderPagesBooklet = (pages) => {
  while (pages.length % 4 !== 0) {
    const spacerPage = new Page();
    spacerPage.element.style.visibility = 'hidden';
    pages.push(spacerPage);
  }
  const bookletOrder = [];
  const len = pages.length;

  for (let i = 0; i < len / 2; i += 2) {
    bookletOrder.push(pages[len - 1 - i]);
    bookletOrder.push(pages[i]);
    bookletOrder.push(pages[i + 1]);
    bookletOrder.push(pages[len - 2 - i]);
  }

  return bookletOrder;
};

const padPages = (pages) => {
  if (pages.length % 2 !== 0) {
    const pg = new Page();
    pages.push(pg);
  }
  const spacerPage = new Page();
  const spacerPage2 = new Page();
  spacerPage.element.style.visibility = 'hidden';
  spacerPage2.element.style.visibility = 'hidden';
  pages.unshift(spacerPage);
  pages.push(spacerPage2);

  return pages;
};

const renderGridLayout = (pages, isTwoUp) => {
  const gridLayout = document.createDocumentFragment();
  if (isTwoUp) {
    for (let i = 0; i < pages.length; i += 2) {
      const wrap = spread(
        { style: Page.spreadSizeStyle() },
        pages[i].element, pages[i + 1].element
      );
      gridLayout.appendChild(wrap);
    }
  } else {
    pages.forEach((pg) => {
      const wrap = spread({ style: Page.sizeStyle() }, pg.element);
      gridLayout.appendChild(wrap);
    });
  }

  return gridLayout;
};

const renderPrintLayout = (pages, isTwoUp, orient, isBooklet) => {
  const printLayout = document.createDocumentFragment();

  const size = isTwoUp ? Page.spreadSizeStyle() : Page.sizeStyle();
  const cropMarks = isTwoUp ? cropMarksSpread : cropMarksSingle;

  const printSheet = function (...arg) {
    return h(`.bindery-print-page.bindery-letter-${orient}`,
      spread({ style: size }, ...arg, cropMarks())
    );
  };

  if (isTwoUp) {
    for (let i = 0; i < pages.length; i += 2) {
      const sheet = printSheet(pages[i].element, pages[i + 1].element);
      printLayout.appendChild(sheet);
      if (isBooklet) {
        const meta = bookletMeta(i, pages.length);
        sheet.appendChild(meta);
      }
    }
  } else {
    pages.forEach((pg) => {
      const sheet = printSheet(pg.element);
      printLayout.appendChild(sheet);
    });
  }

  return printLayout;
};

class Viewer {
  constructor() {
    this.book = null;

    this.zoomBox = h('.bindery-zoom-wrap');
    const spinner = h('.bindery-spinner');
    this.export = h('.bindery-export', this.zoomBox, spinner);

    this.doubleSided = true;
    this.printArrange = ARRANGE_SPREAD;
    this.isShowingCropMarks = true;
    this.setOrientation('landscape');

    this.mode = MODE_PREVIEW;
    this.currentLeaf = 0;

    this.listenForPrint();
    this.listenForResize();

    document.body.appendChild(this.export);
  }

  // Automatically switch into print mode
  listenForPrint() {
    if (window.matchMedia) {
      const mediaQueryList = window.matchMedia('print');
      mediaQueryList.addListener((mql) => {
        if (mql.matches) {
          this.setPrint();
        } else {
          // after print
        }
      });
    }
  }

  listenForResize() {
    window.addEventListener('resize', () => {
      if (!this.throttleResize) {
        this.updateZoom();
        this.throttleResize = setTimeout(() => {
          this.throttleResize = null;
        }, 20);
      }
    });
  }

  get isShowingCropMarks() {
    return this.export.classList.contains('bindery-show-crop');
  }

  set isShowingCropMarks(newVal) {
    if (newVal) {
      this.export.classList.add('bindery-show-crop');
    } else {
      this.export.classList.remove('bindery-show-crop');
    }
  }

  setOrientation(newVal) {
    if (newVal === this.orientation) return;
    this.orientation = newVal;
    setOrientationCSS(newVal);
    if (this.mode === MODE_SHEET) {
      this.update();
    } else {
      this.setPrint();
    }
  }

  setPrintArrange(newVal) {
    if (newVal === this.printArrange) return;
    this.printArrange = newVal;
    if (this.mode === MODE_SHEET) {
      this.update();
    } else {
      this.setPrint();
    }
  }

  displayError(title, text) {
    if (!this.export.parentNode) {
      document.body.appendChild(this.export);
    }
    if (!this.error) {
      this.zoomBox.innerHTML = '';
      this.error = errorView(title, text);
      this.zoomBox.appendChild(this.error);
    }
  }
  clear() {
    this.book = null;
    this.zoomBox.innerHTML = '';
  }
  cancel() {
    // TODO this doesn't work if the target is an existing node
    if (this.export.parentNode) {
      this.export.parentNode.removeChild(this.export);
    }
  }
  toggleGuides() {
    this.export.classList.toggle('bindery-show-guides');
  }
  toggleBleed() {
    this.export.classList.add('bindery-show-bleed');
  }
  toggleDouble() {
    this.doubleSided = !this.doubleSided;
    this.update();
  }
  setMode(newMode) {
    switch (newMode) {
    case 'grid':
    case 'default':
      this.mode = MODE_PREVIEW;
      break;
    case 'interactive':
    case 'flip':
      this.mode = MODE_FLIP;
      break;
    case 'print':
    case 'sheet':
      this.mode = MODE_SHEET;
      break;
    case 'outline':
    case 'outlines':
      this.mode = MODE_OUTLINE;
      break;
    default:
      console.error(`Bindery: Unknown view mode "${newMode}"`);
      break;
    }
  }
  setGrid() {
    if (this.mode === MODE_PREVIEW) return;
    if (this.mode === MODE_OUTLINE) {
      this.mode = MODE_PREVIEW;
      this.updateGuides();
    } else {
      this.mode = MODE_PREVIEW;
      this.update();
    }
  }
  setOutline() {
    if (this.mode === MODE_OUTLINE) return;
    if (this.mode === MODE_PREVIEW) {
      this.mode = MODE_OUTLINE;
      this.updateGuides();
    } else {
      this.mode = MODE_OUTLINE;
      this.update();
    }
  }
  setPrint() {
    if (this.mode === MODE_SHEET) return;
    this.mode = MODE_SHEET;
    this.update();
  }
  setInteractive() {
    this.mode = MODE_FLIP;
    this.update();
  }
  update() {
    if (!this.book) return;
    if (!this.export.parentNode) {
      document.body.appendChild(this.export);
    }

    this.flaps = [];
    document.body.classList.add('bindery-viewing');
    const scrollPct = document.body.scrollTop / document.body.scrollHeight;

    if (this.mode === MODE_PREVIEW) {
      this.renderGrid();
    } else if (this.mode === MODE_OUTLINE) {
      this.renderGrid();
    } else if (this.mode === MODE_FLIP) {
      this.renderInteractive();
    } else if (this.mode === MODE_SHEET) {
      this.renderPrint();
    } else {
      this.renderGrid();
    }

    document.body.scrollTop = document.body.scrollHeight * scrollPct;
    this.updateZoom();
  }

  updateZoom() {
    const scrollPct = document.body.scrollTop / document.body.scrollHeight;
    const exportW = this.zoomBox.getBoundingClientRect().width;
    const contentW = this.zoomBox.firstElementChild.getBoundingClientRect().width;

    const scale = Math.min(1, exportW / (contentW + 20));

    this.zoomBox.style.transform = `scale(${scale})`;
    document.body.scrollTop = document.body.scrollHeight * scrollPct;
  }

  updateGuides() {
    if (this.mode === MODE_OUTLINE) {
      this.export.classList.add('bindery-show-bleed');
      this.export.classList.add('bindery-show-guides');
    } else {
      this.export.classList.remove('bindery-show-bleed');
      this.export.classList.remove('bindery-show-guides');
    }
  }

  renderPrint() {
    this.export.classList.add('bindery-show-bleed');
    this.export.classList.remove('bindery-show-guides');

    this.zoomBox.innerHTML = '';

    const isTwoUp = this.printArrange !== ARRANGE_ONE;
    const isBooklet = this.printArrange === ARRANGE_BOOKLET;
    const orient = this.orientation;

    let pages = this.book.pages.slice();
    if (this.printArrange === ARRANGE_SPREAD) {
      pages = padPages(pages);
    } else if (this.printArrange === ARRANGE_BOOKLET) {
      pages = orderPagesBooklet(pages);
    }

    const printLayout = renderPrintLayout(pages, isTwoUp, orient, isBooklet);
    this.zoomBox.appendChild(printLayout);
  }

  renderGrid() {
    this.updateGuides();
    this.zoomBox.innerHTML = '';

    let pages = this.book.pages.slice();

    if (this.doubleSided) {
      pages = padPages(pages);
    }

    const gridLayout = renderGridLayout(pages, this.doubleSided);
    this.zoomBox.appendChild(gridLayout);
  }

  renderInteractive() {
    this.zoomBox.innerHTML = '';
    this.flaps = [];

    this.export.classList.remove('bindery-show-bleed');
    this.export.classList.remove('bindery-show-guides');

    const pages = padPages(this.book.pages.slice());

    let leafIndex = 0;
    for (let i = 1; i < pages.length - 1; i += (this.doubleSided ? 2 : 1)) {
      leafIndex += 1;
      const li = leafIndex;
      const flap = h('.bindery-page3d', {
        style: Page.sizeStyle(),
        onclick: () => {
          let newLeaf = li - 1;
          if (newLeaf === this.currentLeaf) newLeaf += 1;
          this.setLeaf(newLeaf);
        },
      });
      this.export.classList.add('bindery-stage3d');
      this.flaps.push(flap);

      const rightPage = pages[i].element;
      let leftPage;
      rightPage.classList.add('bindery-page3d-front');
      flap.appendChild(rightPage);
      if (this.doubleSided) {
        flap.classList.add('bindery-doubleSided');
        leftPage = pages[i + 1].element;
        leftPage.classList.add('bindery-page3d-back');
        flap.appendChild(leftPage);
      } else {
        leftPage = h('.bindery-page.bindery-page3d-back', {
          style: Page.sizeStyle(),
        });
        flap.appendChild(leftPage);
      }
      // TODO: Dynamically add/remove pages.
      // Putting 1000s of elements onscreen
      // locks up the browser.

      let leftOffset = 4;
      if (pages.length * leftOffset > 300) leftOffset = 300 / pages.length;

      flap.style.left = `${i * leftOffset}px`;

      this.zoomBox.appendChild(flap);
    }
    if (this.currentLeaf) {
      this.setLeaf(this.currentLeaf);
    } else {
      this.setLeaf(0);
    }
  }

  setLeaf(n) {
    this.currentLeaf = n;
    let zScale = 4;
    if (this.flaps.length * zScale > 200) zScale = 200 / this.flaps.length;

    this.flaps.forEach((flap, i, arr) => {
      // + 0.5 so left and right are even
      const z = (arr.length - Math.abs((i - n) + 0.5)) * zScale;
      flap.style.transform = `translate3d(${(i < n) ? 4 : 0}px,0,${z}px) rotateY(${(i < n) ? -180 : 0}deg)`;
    });
  }
}

export default Viewer;
<commit_msg>Don't zoom when theres no content<commit_after>import h from 'hyperscript';

import Page from '../Page';
import errorView from './error';

const MODE_FLIP = 'interactive';
const MODE_PREVIEW = 'grid';
const MODE_SHEET = 'print';
const MODE_OUTLINE = 'outline';

const ARRANGE_ONE = 'arrange_one';
const ARRANGE_SPREAD = 'arrange_two';
const ARRANGE_BOOKLET = 'arrange_booklet';
// const ARRANGE_SIGNATURE = 'arrange_signature';

const cropMarksSingle = () => h('.bindery-crop-wrap',
  h('.bindery-crop-top'),
  h('.bindery-crop-bottom'),
  h('.bindery-crop-left'),
  h('.bindery-crop-right'),
);
const cropMarksSpread = () => h('.bindery-crop-wrap',
  h('.bindery-crop-top'),
  h('.bindery-crop-bottom'),
  h('.bindery-crop-left'),
  h('.bindery-crop-right'),
  h('.bindery-crop-fold'),
);

const spread = function (...arg) {
  return h('.bindery-spread-wrapper', ...arg);
};

const bookletMeta = (i, len) => {
  const isFront = i % 4 === 0;
  const sheetIndex = parseInt((i + 1) / 4, 10) + 1;
  return h('.bindery-print-meta',
    `Sheet ${sheetIndex} of ${len / 4}: ${isFront ? 'Outside' : 'Inside'}`);
};

const ORIENTATION_STYLE_ID = 'bindery-orientation-stylesheet';

const setOrientationCSS = (newValue) => {
  let sheet;
  const existing = document.querySelector(`#${ORIENTATION_STYLE_ID}`);
  if (existing) {
    sheet = existing;
  } else {
    sheet = document.createElement('style');
    sheet.id = ORIENTATION_STYLE_ID;
  }
  sheet.innerHTML = `@page { size: ${newValue}; }`;
  document.head.appendChild(sheet);
};

const orderPagesBooklet = (pages) => {
  while (pages.length % 4 !== 0) {
    const spacerPage = new Page();
    spacerPage.element.style.visibility = 'hidden';
    pages.push(spacerPage);
  }
  const bookletOrder = [];
  const len = pages.length;

  for (let i = 0; i < len / 2; i += 2) {
    bookletOrder.push(pages[len - 1 - i]);
    bookletOrder.push(pages[i]);
    bookletOrder.push(pages[i + 1]);
    bookletOrder.push(pages[len - 2 - i]);
  }

  return bookletOrder;
};

const padPages = (pages) => {
  if (pages.length % 2 !== 0) {
    const pg = new Page();
    pages.push(pg);
  }
  const spacerPage = new Page();
  const spacerPage2 = new Page();
  spacerPage.element.style.visibility = 'hidden';
  spacerPage2.element.style.visibility = 'hidden';
  pages.unshift(spacerPage);
  pages.push(spacerPage2);

  return pages;
};

const renderGridLayout = (pages, isTwoUp) => {
  const gridLayout = document.createDocumentFragment();
  if (isTwoUp) {
    for (let i = 0; i < pages.length; i += 2) {
      const wrap = spread(
        { style: Page.spreadSizeStyle() },
        pages[i].element, pages[i + 1].element
      );
      gridLayout.appendChild(wrap);
    }
  } else {
    pages.forEach((pg) => {
      const wrap = spread({ style: Page.sizeStyle() }, pg.element);
      gridLayout.appendChild(wrap);
    });
  }

  return gridLayout;
};

const renderPrintLayout = (pages, isTwoUp, orient, isBooklet) => {
  const printLayout = document.createDocumentFragment();

  const size = isTwoUp ? Page.spreadSizeStyle() : Page.sizeStyle();
  const cropMarks = isTwoUp ? cropMarksSpread : cropMarksSingle;

  const printSheet = function (...arg) {
    return h(`.bindery-print-page.bindery-letter-${orient}`,
      spread({ style: size }, ...arg, cropMarks())
    );
  };

  if (isTwoUp) {
    for (let i = 0; i < pages.length; i += 2) {
      const sheet = printSheet(pages[i].element, pages[i + 1].element);
      printLayout.appendChild(sheet);
      if (isBooklet) {
        const meta = bookletMeta(i, pages.length);
        sheet.appendChild(meta);
      }
    }
  } else {
    pages.forEach((pg) => {
      const sheet = printSheet(pg.element);
      printLayout.appendChild(sheet);
    });
  }

  return printLayout;
};

class Viewer {
  constructor() {
    this.book = null;

    this.zoomBox = h('.bindery-zoom-wrap');
    const spinner = h('.bindery-spinner');
    this.export = h('.bindery-export', this.zoomBox, spinner);

    this.doubleSided = true;
    this.printArrange = ARRANGE_SPREAD;
    this.isShowingCropMarks = true;
    this.setOrientation('landscape');

    this.mode = MODE_PREVIEW;
    this.currentLeaf = 0;

    this.listenForPrint();
    this.listenForResize();

    document.body.appendChild(this.export);
  }

  // Automatically switch into print mode
  listenForPrint() {
    if (window.matchMedia) {
      const mediaQueryList = window.matchMedia('print');
      mediaQueryList.addListener((mql) => {
        if (mql.matches) {
          this.setPrint();
        } else {
          // after print
        }
      });
    }
  }

  listenForResize() {
    window.addEventListener('resize', () => {
      if (!this.throttleResize) {
        this.updateZoom();
        this.throttleResize = setTimeout(() => {
          this.throttleResize = null;
        }, 20);
      }
    });
  }

  get isShowingCropMarks() {
    return this.export.classList.contains('bindery-show-crop');
  }

  set isShowingCropMarks(newVal) {
    if (newVal) {
      this.export.classList.add('bindery-show-crop');
    } else {
      this.export.classList.remove('bindery-show-crop');
    }
  }

  setOrientation(newVal) {
    if (newVal === this.orientation) return;
    this.orientation = newVal;
    setOrientationCSS(newVal);
    if (this.mode === MODE_SHEET) {
      this.update();
    } else {
      this.setPrint();
    }
  }

  setPrintArrange(newVal) {
    if (newVal === this.printArrange) return;
    this.printArrange = newVal;
    if (this.mode === MODE_SHEET) {
      this.update();
    } else {
      this.setPrint();
    }
  }

  displayError(title, text) {
    if (!this.export.parentNode) {
      document.body.appendChild(this.export);
    }
    if (!this.error) {
      this.zoomBox.innerHTML = '';
      this.error = errorView(title, text);
      this.zoomBox.appendChild(this.error);
    }
  }
  clear() {
    this.book = null;
    this.zoomBox.innerHTML = '';
  }
  cancel() {
    // TODO this doesn't work if the target is an existing node
    if (this.export.parentNode) {
      this.export.parentNode.removeChild(this.export);
    }
  }
  toggleGuides() {
    this.export.classList.toggle('bindery-show-guides');
  }
  toggleBleed() {
    this.export.classList.add('bindery-show-bleed');
  }
  toggleDouble() {
    this.doubleSided = !this.doubleSided;
    this.update();
  }
  setMode(newMode) {
    switch (newMode) {
    case 'grid':
    case 'default':
      this.mode = MODE_PREVIEW;
      break;
    case 'interactive':
    case 'flip':
      this.mode = MODE_FLIP;
      break;
    case 'print':
    case 'sheet':
      this.mode = MODE_SHEET;
      break;
    case 'outline':
    case 'outlines':
      this.mode = MODE_OUTLINE;
      break;
    default:
      console.error(`Bindery: Unknown view mode "${newMode}"`);
      break;
    }
  }
  setGrid() {
    if (this.mode === MODE_PREVIEW) return;
    if (this.mode === MODE_OUTLINE) {
      this.mode = MODE_PREVIEW;
      this.updateGuides();
    } else {
      this.mode = MODE_PREVIEW;
      this.update();
    }
  }
  setOutline() {
    if (this.mode === MODE_OUTLINE) return;
    if (this.mode === MODE_PREVIEW) {
      this.mode = MODE_OUTLINE;
      this.updateGuides();
    } else {
      this.mode = MODE_OUTLINE;
      this.update();
    }
  }
  setPrint() {
    if (this.mode === MODE_SHEET) return;
    this.mode = MODE_SHEET;
    this.update();
  }
  setInteractive() {
    this.mode = MODE_FLIP;
    this.update();
  }
  update() {
    if (!this.book) return;
    if (!this.export.parentNode) {
      document.body.appendChild(this.export);
    }

    this.flaps = [];
    document.body.classList.add('bindery-viewing');
    const scrollPct = document.body.scrollTop / document.body.scrollHeight;

    if (this.mode === MODE_PREVIEW) {
      this.renderGrid();
    } else if (this.mode === MODE_OUTLINE) {
      this.renderGrid();
    } else if (this.mode === MODE_FLIP) {
      this.renderInteractive();
    } else if (this.mode === MODE_SHEET) {
      this.renderPrint();
    } else {
      this.renderGrid();
    }

    document.body.scrollTop = document.body.scrollHeight * scrollPct;
    this.updateZoom();
  }

  updateZoom() {
    if (this.zoomBox.firstElementChild) {
      const scrollPct = document.body.scrollTop / document.body.scrollHeight;
      const exportW = this.zoomBox.getBoundingClientRect().width;
      const contentW = this.zoomBox.firstElementChild.getBoundingClientRect().width;

      const scale = Math.min(1, exportW / (contentW + 20));

      this.zoomBox.style.transform = `scale(${scale})`;
      document.body.scrollTop = document.body.scrollHeight * scrollPct;
    }
  }

  updateGuides() {
    if (this.mode === MODE_OUTLINE) {
      this.export.classList.add('bindery-show-bleed');
      this.export.classList.add('bindery-show-guides');
    } else {
      this.export.classList.remove('bindery-show-bleed');
      this.export.classList.remove('bindery-show-guides');
    }
  }

  renderPrint() {
    this.export.classList.add('bindery-show-bleed');
    this.export.classList.remove('bindery-show-guides');

    this.zoomBox.innerHTML = '';

    const isTwoUp = this.printArrange !== ARRANGE_ONE;
    const isBooklet = this.printArrange === ARRANGE_BOOKLET;
    const orient = this.orientation;

    let pages = this.book.pages.slice();
    if (this.printArrange === ARRANGE_SPREAD) {
      pages = padPages(pages);
    } else if (this.printArrange === ARRANGE_BOOKLET) {
      pages = orderPagesBooklet(pages);
    }

    const printLayout = renderPrintLayout(pages, isTwoUp, orient, isBooklet);
    this.zoomBox.appendChild(printLayout);
  }

  renderGrid() {
    this.updateGuides();
    this.zoomBox.innerHTML = '';

    let pages = this.book.pages.slice();

    if (this.doubleSided) {
      pages = padPages(pages);
    }

    const gridLayout = renderGridLayout(pages, this.doubleSided);
    this.zoomBox.appendChild(gridLayout);
  }

  renderInteractive() {
    this.zoomBox.innerHTML = '';
    this.flaps = [];

    this.export.classList.remove('bindery-show-bleed');
    this.export.classList.remove('bindery-show-guides');

    const pages = padPages(this.book.pages.slice());

    let leafIndex = 0;
    for (let i = 1; i < pages.length - 1; i += (this.doubleSided ? 2 : 1)) {
      leafIndex += 1;
      const li = leafIndex;
      const flap = h('.bindery-page3d', {
        style: Page.sizeStyle(),
        onclick: () => {
          let newLeaf = li - 1;
          if (newLeaf === this.currentLeaf) newLeaf += 1;
          this.setLeaf(newLeaf);
        },
      });
      this.export.classList.add('bindery-stage3d');
      this.flaps.push(flap);

      const rightPage = pages[i].element;
      let leftPage;
      rightPage.classList.add('bindery-page3d-front');
      flap.appendChild(rightPage);
      if (this.doubleSided) {
        flap.classList.add('bindery-doubleSided');
        leftPage = pages[i + 1].element;
        leftPage.classList.add('bindery-page3d-back');
        flap.appendChild(leftPage);
      } else {
        leftPage = h('.bindery-page.bindery-page3d-back', {
          style: Page.sizeStyle(),
        });
        flap.appendChild(leftPage);
      }
      // TODO: Dynamically add/remove pages.
      // Putting 1000s of elements onscreen
      // locks up the browser.

      let leftOffset = 4;
      if (pages.length * leftOffset > 300) leftOffset = 300 / pages.length;

      flap.style.left = `${i * leftOffset}px`;

      this.zoomBox.appendChild(flap);
    }
    if (this.currentLeaf) {
      this.setLeaf(this.currentLeaf);
    } else {
      this.setLeaf(0);
    }
  }

  setLeaf(n) {
    this.currentLeaf = n;
    let zScale = 4;
    if (this.flaps.length * zScale > 200) zScale = 200 / this.flaps.length;

    this.flaps.forEach((flap, i, arr) => {
      // + 0.5 so left and right are even
      const z = (arr.length - Math.abs((i - n) + 0.5)) * zScale;
      flap.style.transform = `translate3d(${(i < n) ? 4 : 0}px,0,${z}px) rotateY(${(i < n) ? -180 : 0}deg)`;
    });
  }
}

export default Viewer;
<|endoftext|>


<commit_before>d3.rebind(pc, axis, "ticks", "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat");

function flipAxisAndUpdatePCP(dimension) {
  var g = pc.svg.selectAll(".dimension");

  pc.flip(dimension);

  d3.select(this.parentElement)
    .transition()
      .duration(1100)
      .call(axis.scale(yscale[dimension]));

  pc.render();
}

function rotateLabels() {
  var delta = d3.event.deltaY;
  delta = delta < 0 ? -5 : delta;
  delta = delta > 0 ? 5 : delta;

  __.dimensionTitleRotation += delta;
  pc.svg.selectAll("text.label")
    .attr("transform", "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")");
  d3.event.preventDefault();
}

function dimensionLabels(d) {
  return d in __.dimensionTitles ? __.dimensionTitles[d] : d;  // dimension display names
}

pc.createAxes = function() {
  if (g) pc.removeAxes();

  // Add a group element for each dimension.
  g = pc.svg.selectAll(".dimension")
      .data(__.dimensions, function(d) { return d; })
    .enter().append("svg:g")
      .attr("class", "dimension")
      .attr("transform", function(d) { return "translate(" + xscale(d) + ")"; });

  // Add an axis and title.
  g.append("svg:g")
      .attr("class", "axis")
      .attr("transform", "translate(0,0)")
      .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
    .append("svg:text")
      .attr({
        "text-anchor": "middle",
        "y": 0,
        "transform": "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")",
        "x": 0,
        "class": "label"
      })
      .text(dimensionLabels)
      .on("dblclick", flipAxisAndUpdatePCP)
      .on("wheel", rotateLabels);

  flags.axes= true;
  return this;
};

pc.removeAxes = function() {
  g.remove();
  return this;
};

pc.updateAxes = function() {
  var g_data = pc.svg.selectAll(".dimension").data(__.dimensions);

  // Enter
  g_data.enter().append("svg:g")
      .attr("class", "dimension")
      .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
      .style("opacity", 0)
    .append("svg:g")
      .attr("class", "axis")
      .attr("transform", "translate(0,0)")
      .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
    .append("svg:text")
      .attr({
        "text-anchor": "middle",
        "y": 0,
        "transform": "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")",
        "x": 0,
        "class": "label"
      })
      .text(dimensionLabels)
      .on("dblclick", flipAxisAndUpdatePCP)
      .on("wheel", rotateLabels);

  // Update
  g_data.attr("opacity", 0);
  g_data.select(".axis")
    .transition()
      .duration(1100)
      .each(function(d) {
        d3.select(this).call(axis.scale(yscale[d]));
      });
  g_data.select(".label")
    .transition()
      .duration(1100)
      .text(dimensionLabels)
      .attr("transform", "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")");

  // Exit
  g_data.exit().remove();

  g = pc.svg.selectAll(".dimension");
  g.transition().duration(1100)
    .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
    .style("opacity", 1);

  pc.svg.selectAll(".axis")
    .transition()
      .duration(1100)
      .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); });

  if (flags.brushable) pc.brushable();
  if (flags.reorderable) pc.reorderable();
  if (pc.brushMode() !== "None") {
    var mode = pc.brushMode();
    pc.brushMode("None");
    pc.brushMode(mode);
  }
  return this;
};

// Jason Davies, http://bl.ocks.org/1341281
pc.reorderable = function() {
  if (!g) pc.createAxes();

  g.style("cursor", "move")
    .call(d3.behavior.drag()
      .on("dragstart", function(d) {
        dragging[d] = this.__origin__ = xscale(d);
      })
      .on("drag", function(d) {
        dragging[d] = Math.min(w(), Math.max(0, this.__origin__ += d3.event.dx));
        __.dimensions.sort(function(a, b) { return position(a) - position(b); });
        xscale.domain(__.dimensions);
        pc.render();
        g.attr("transform", function(d) { return "translate(" + position(d) + ")"; });
      })
      .on("dragend", function(d) {
        // Let's see if the order has changed and send out an event if so.
        var i = 0,
            j = __.dimensions.indexOf(d),
            elem = this,
            parent = this.parentElement;

        while((elem = elem.previousElementSibling) != null) ++i;
        if (i !== j) {
          events.axesreorder.call(pc, __.dimensions);
          // We now also want to reorder the actual dom elements that represent
          // the axes. That is, the g.dimension elements. If we don't do this,
          // we get a weird and confusing transition when updateAxes is called.
          // This is due to the fact that, initially the nth g.dimension element
          // represents the nth axis. However, after a manual reordering,
          // without reordering the dom elements, the nth dom elements no longer
          // necessarily represents the nth axis.
          //
          // i is the original index of the dom element
          // j is the new index of the dom element
          if (i > j) { // Element moved left
            parent.insertBefore(this, parent.children[j - 1]);
          } else {     // Element moved right
            if ((j + 1) < parent.children.length) {
              parent.insertBefore(this, parent.children[j + 1]);
            } else {
              parent.appendChild(this);
            }
          }
        }

        delete this.__origin__;
        delete dragging[d];
        d3.select(this).transition().attr("transform", "translate(" + xscale(d) + ")");
        pc.render();
      }));
  flags.reorderable = true;
  return this;
};

// Reorder dimensions, such that the highest value (visually) is on the left and
// the lowest on the right. Visual values are determined by the data values in
// the given row.
pc.reorder = function(rowdata) {
  var dims = __.dimensions.slice(0);
  __.dimensions.sort(function(a, b) {
    var pixelDifference = yscale[a](rowdata[a]) - yscale[b](rowdata[b]);

    // Array.sort is not necessarily stable, this means that if pixelDifference is zero
    // the ordering of dimensions might change unexpectedly. This is solved by sorting on
    // variable name in that case.
    if (pixelDifference === 0) {
      return a.localeCompare(b);
    } // else
    return pixelDifference;
  });

  // NOTE: this is relatively cheap given that:
  // number of dimensions < number of data items
  // Thus we check equality of order to prevent rerendering when this is the case.
  var reordered = false;
  dims.some(function(val, index) {
    reordered = val !== __.dimensions[index];
    return reordered;
  });

  if (reordered) {
    xscale.domain(__.dimensions);
    var highlighted = __.highlighted.slice(0);
    pc.unhighlight();

    g.transition()
      .duration(1500)
      .attr("transform", function(d) {
        return "translate(" + xscale(d) + ")";
      });
    pc.render();

    // pc.highlight() does not check whether highlighted is length zero, so we do that here.
    if (highlighted.length !== 0) {
      pc.highlight(highlighted);
    }
  }
}

// pairs of adjacent dimensions
pc.adjacent_pairs = function(arr) {
  var ret = [];
  for (var i = 0; i < arr.length-1; i++) {
    ret.push([arr[i],arr[i+1]]);
  };
  return ret;
};
<commit_msg>fully remove g when `removeAxes`<commit_after>d3.rebind(pc, axis, "ticks", "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat");

function flipAxisAndUpdatePCP(dimension) {
  var g = pc.svg.selectAll(".dimension");

  pc.flip(dimension);

  d3.select(this.parentElement)
    .transition()
      .duration(1100)
      .call(axis.scale(yscale[dimension]));

  pc.render();
}

function rotateLabels() {
  var delta = d3.event.deltaY;
  delta = delta < 0 ? -5 : delta;
  delta = delta > 0 ? 5 : delta;

  __.dimensionTitleRotation += delta;
  pc.svg.selectAll("text.label")
    .attr("transform", "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")");
  d3.event.preventDefault();
}

function dimensionLabels(d) {
  return d in __.dimensionTitles ? __.dimensionTitles[d] : d;  // dimension display names
}

pc.createAxes = function() {
  if (g) pc.removeAxes();

  // Add a group element for each dimension.
  g = pc.svg.selectAll(".dimension")
      .data(__.dimensions, function(d) { return d; })
    .enter().append("svg:g")
      .attr("class", "dimension")
      .attr("transform", function(d) { return "translate(" + xscale(d) + ")"; });

  // Add an axis and title.
  g.append("svg:g")
      .attr("class", "axis")
      .attr("transform", "translate(0,0)")
      .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
    .append("svg:text")
      .attr({
        "text-anchor": "middle",
        "y": 0,
        "transform": "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")",
        "x": 0,
        "class": "label"
      })
      .text(dimensionLabels)
      .on("dblclick", flipAxisAndUpdatePCP)
      .on("wheel", rotateLabels);

  flags.axes= true;
  return this;
};

pc.removeAxes = function() {
  g.remove();
  g = undefined;
  return this;
};

pc.updateAxes = function() {
  var g_data = pc.svg.selectAll(".dimension").data(__.dimensions);

  // Enter
  g_data.enter().append("svg:g")
      .attr("class", "dimension")
      .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
      .style("opacity", 0)
    .append("svg:g")
      .attr("class", "axis")
      .attr("transform", "translate(0,0)")
      .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
    .append("svg:text")
      .attr({
        "text-anchor": "middle",
        "y": 0,
        "transform": "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")",
        "x": 0,
        "class": "label"
      })
      .text(dimensionLabels)
      .on("dblclick", flipAxisAndUpdatePCP)
      .on("wheel", rotateLabels);

  // Update
  g_data.attr("opacity", 0);
  g_data.select(".axis")
    .transition()
      .duration(1100)
      .each(function(d) {
        d3.select(this).call(axis.scale(yscale[d]));
      });
  g_data.select(".label")
    .transition()
      .duration(1100)
      .text(dimensionLabels)
      .attr("transform", "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")");

  // Exit
  g_data.exit().remove();

  g = pc.svg.selectAll(".dimension");
  g.transition().duration(1100)
    .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
    .style("opacity", 1);

  pc.svg.selectAll(".axis")
    .transition()
      .duration(1100)
      .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); });

  if (flags.brushable) pc.brushable();
  if (flags.reorderable) pc.reorderable();
  if (pc.brushMode() !== "None") {
    var mode = pc.brushMode();
    pc.brushMode("None");
    pc.brushMode(mode);
  }
  return this;
};

// Jason Davies, http://bl.ocks.org/1341281
pc.reorderable = function() {
  if (!g) pc.createAxes();

  g.style("cursor", "move")
    .call(d3.behavior.drag()
      .on("dragstart", function(d) {
        dragging[d] = this.__origin__ = xscale(d);
      })
      .on("drag", function(d) {
        dragging[d] = Math.min(w(), Math.max(0, this.__origin__ += d3.event.dx));
        __.dimensions.sort(function(a, b) { return position(a) - position(b); });
        xscale.domain(__.dimensions);
        pc.render();
        g.attr("transform", function(d) { return "translate(" + position(d) + ")"; });
      })
      .on("dragend", function(d) {
        // Let's see if the order has changed and send out an event if so.
        var i = 0,
            j = __.dimensions.indexOf(d),
            elem = this,
            parent = this.parentElement;

        while((elem = elem.previousElementSibling) != null) ++i;
        if (i !== j) {
          events.axesreorder.call(pc, __.dimensions);
          // We now also want to reorder the actual dom elements that represent
          // the axes. That is, the g.dimension elements. If we don't do this,
          // we get a weird and confusing transition when updateAxes is called.
          // This is due to the fact that, initially the nth g.dimension element
          // represents the nth axis. However, after a manual reordering,
          // without reordering the dom elements, the nth dom elements no longer
          // necessarily represents the nth axis.
          //
          // i is the original index of the dom element
          // j is the new index of the dom element
          if (i > j) { // Element moved left
            parent.insertBefore(this, parent.children[j - 1]);
          } else {     // Element moved right
            if ((j + 1) < parent.children.length) {
              parent.insertBefore(this, parent.children[j + 1]);
            } else {
              parent.appendChild(this);
            }
          }
        }

        delete this.__origin__;
        delete dragging[d];
        d3.select(this).transition().attr("transform", "translate(" + xscale(d) + ")");
        pc.render();
      }));
  flags.reorderable = true;
  return this;
};

// Reorder dimensions, such that the highest value (visually) is on the left and
// the lowest on the right. Visual values are determined by the data values in
// the given row.
pc.reorder = function(rowdata) {
  var dims = __.dimensions.slice(0);
  __.dimensions.sort(function(a, b) {
    var pixelDifference = yscale[a](rowdata[a]) - yscale[b](rowdata[b]);

    // Array.sort is not necessarily stable, this means that if pixelDifference is zero
    // the ordering of dimensions might change unexpectedly. This is solved by sorting on
    // variable name in that case.
    if (pixelDifference === 0) {
      return a.localeCompare(b);
    } // else
    return pixelDifference;
  });

  // NOTE: this is relatively cheap given that:
  // number of dimensions < number of data items
  // Thus we check equality of order to prevent rerendering when this is the case.
  var reordered = false;
  dims.some(function(val, index) {
    reordered = val !== __.dimensions[index];
    return reordered;
  });

  if (reordered) {
    xscale.domain(__.dimensions);
    var highlighted = __.highlighted.slice(0);
    pc.unhighlight();

    g.transition()
      .duration(1500)
      .attr("transform", function(d) {
        return "translate(" + xscale(d) + ")";
      });
    pc.render();

    // pc.highlight() does not check whether highlighted is length zero, so we do that here.
    if (highlighted.length !== 0) {
      pc.highlight(highlighted);
    }
  }
}

// pairs of adjacent dimensions
pc.adjacent_pairs = function(arr) {
  var ret = [];
  for (var i = 0; i < arr.length-1; i++) {
    ret.push([arr[i],arr[i+1]]);
  };
  return ret;
};
<|endoftext|>


<commit_before>var Player_actions = require("./player_actions");
var ranking = require("./ranking.js");
var minimumToCall = 10;
var minimumToRaise = 20;

module.exports = {

  VERSION: "Default JavaScript folding player",

  bet_request: function(game_state, bet) {
    var action = new Player_actions(game_state);
    var handRank = ranking.getHandValue(game_state.players[game_state.in_action]["hole_cards"]);

    if (handRank < minimumToCall) {
      bet(action.doFold());
    } else if (handRank >= minimumToCall && handRank < minimumToRaise) {
      bet(action.doCall());
    } else {
      bet(action.doAllIn());
    }
  },

  showdown: function(game_state) {

  }
};
<commit_msg>statics change<commit_after>var Player_actions = require("./player_actions");
var ranking = require("./ranking.js");
var minimumToCall = 18;
var minimumToRaise = 22;

module.exports = {

  VERSION: "Default JavaScript folding player",

  bet_request: function(game_state, bet) {
    var action = new Player_actions(game_state);
    var handRank = ranking.getHandValue(game_state.players[game_state.in_action]["hole_cards"]);

    if (handRank < minimumToCall) {
      bet(action.doFold());
    } else if (handRank >= minimumToCall && handRank < minimumToRaise) {
      bet(action.doCall());
    } else {
      bet(action.doAllIn());
    }
  },

  showdown: function(game_state) {

  }
};
<|endoftext|>


<commit_before>/*!
 * Start Bootstrap - Agnecy Bootstrap Theme (http://startbootstrap.com)
 * Code licensed under the Apache License v2.0.
 * For details, see http://www.apache.org/licenses/LICENSE-2.0.
 */

// jQuery for page scrolling feature - requires jQuery Easing plugin
$(function() {
    $('a.page-scroll').bind('click', function(event) {
        var $anchor = $(this);
        $('html, body').stop().animate({
            scrollTop: $($anchor.attr('href')).offset().top
        }, 1500, 'easeInOutExpo');
        event.preventDefault();
    });
});

// Highlight the top nav as scrolling occurs
$('body').scrollspy({
    target: '.navbar-fixed-top'
})

// Closes the Responsive Menu on Menu Item Click
$('.navbar-collapse ul li a').click(function() {
    $('.navbar-toggle:visible').click();
});

$('div.modal').on('show.bs.modal', function() {
	var modal = this;
	var hash = modal.id;
	window.location.hash = hash;
	window.onhashchange = function() {
		if (!location.hash){
			$(modal).modal('hide');
		}
	}
});

$('div.modal').on('hidden.bs.modal', function (e) {
  var modal = this;
	var hash = modal.id;
  var videoFrame = modal.iframe;
  print(modal);
    //   $(videoFrame).attr("src", $(videoFrame).attr("src"));
});
<commit_msg>debuggings<commit_after>/*!
 * Start Bootstrap - Agnecy Bootstrap Theme (http://startbootstrap.com)
 * Code licensed under the Apache License v2.0.
 * For details, see http://www.apache.org/licenses/LICENSE-2.0.
 */

// jQuery for page scrolling feature - requires jQuery Easing plugin
$(function() {
    $('a.page-scroll').bind('click', function(event) {
        var $anchor = $(this);
        $('html, body').stop().animate({
            scrollTop: $($anchor.attr('href')).offset().top
        }, 1500, 'easeInOutExpo');
        event.preventDefault();
    });
});

// Highlight the top nav as scrolling occurs
$('body').scrollspy({
    target: '.navbar-fixed-top'
})

// Closes the Responsive Menu on Menu Item Click
$('.navbar-collapse ul li a').click(function() {
    $('.navbar-toggle:visible').click();
});

$('div.modal').on('show.bs.modal', function() {
	var modal = this;
	var hash = modal.id;
	window.location.hash = hash;
	window.onhashchange = function() {
		if (!location.hash){
			$(modal).modal('hide');
		}
	}
});

$('div.modal').on('hidden.bs.modal', function (e) {
  var modal = this;
	var hash = modal.id;
  var videoFrame = modal.iframe;
  console.log(videoFrame);
    //   $(videoFrame).attr("src", $(videoFrame).attr("src"));
});
<|endoftext|>


<commit_before>/* Paasmaker - Platform as a Service
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * pm.data.js - wrapper to manage Web Sockets and JSON API calls from frontend code
 */

if (!window.pm) { var pm = {}; }	// TODO: module handling

// For short-term compatibility, keep this as a global object.
// Later we can make it private and require the use of getSocket.
var streamSocket;

pm.data = (function() {

	var screenTimeouts = [];

	return {
		/**
		 * Generic error handler for ajax requests sent via pm.data: try to parse the response
		 * body and show a Bootstrap alert box at the top of the page with the errors array.
		 * TODO: fails for non-JSON responses, and assumes only one .loading-overlay at a time
		 *
		 * @param options argument object from pm.data.api or pm.data.post
		 */
		xhrErrorHandler: function(options) {
			return function(result) {
				$('.loading-overlay').remove();
				var responseData = JSON.parse(result.responseText);
				if (responseData.data.input_errors) {
					for(var key in responseData.data.input_errors) {
						responseData.errors.push(key + ": " + responseData.data.input_errors[key]);
					}
				}
				if ($('#main_right_view').length) {
					$('#main_right_view').prepend(Handlebars.templates.error_block(responseData));
				} else {
					$('#main').prepend(Handlebars.templates.error_block(responseData));
				}

				if (options.errorCallback) {
					options.errorCallback.apply(this, arguments);
				}
			}
		},

		/**
		 * Wrapper for GET calls to the pacemaker's JSON API:
		 *  - appends ?format=json so other code doesn't need to
		 *  - parses JSON and gets out the .data property
		 *  - detects (but doesn't yet handle) errors and warnings
		 *
		 * @param options object with these properties:
		 *  - endpoint: URL of the API endpoint to call
		 *  - callback: function to call when request completes
		 *  - arguments: (optional) key-value mapping of arguments to append to the URL
		 *  - warningCallback: (optional) function to call when API returns a warning
		 *  - errorCallback: (optional) function to call when API returns an error
		 */
		api: function(options) {
			if (!options.arguments) { options.arguments = {}; }

			if (options.endpoint.substr(0, 1) !== '/') {
				options.endpoint = '/' + options.endpoint;
			}

			if (options.endpoint.indexOf('?') !== -1) {
				// parse out existing arguments if there are any in the endpoint string
				var query_string = options.endpoint.split('?');
				options.endpoint = query_string[0];

				query_string[1].split('&').forEach(function(arg) {
					arg_parts = arg.split('=');
					options.arguments[decodeURIComponent(arg_parts[0])] = decodeURIComponent(arg_parts[1]);
				});
			}

			// make sure ?format=json is included, then rebuild the query string
			options.arguments.format = "json";
			var arg_string = '';
			for (var key in options.arguments) {
				if (arg_string != '') { arg_string += '&'; }
				arg_string += encodeURIComponent(key) + '=' + encodeURIComponent(options.arguments[key]);
			}

			$.ajax({
				dataType: "json",
				url: options.endpoint + '?' + arg_string,
				error: pm.data.xhrErrorHandler(options),
				success: function(responseData) {
					if (responseData.errors && responseData.errors.length > 0) {
						if (options.errorCallback) {
							options.errorCallback.apply(this, arguments);
						}
					}
					if (responseData.warnings && responseData.warnings.length > 0) {
						if (options.warningCallback) {
							options.warningCallback.apply(this, arguments);
						}
					}

					// make sure other arguments are passed unchanged
					var modified_args = arguments;
					modified_args[0] = responseData.data;

					options.callback.apply(this, modified_args);
				}
			});
		},

		/**
		 * Wrapper for POST calls to the pacemaker's JSON API:
		 *  - appends ?format=json so other code doesn't need to
		 *  - parses JSON and gets out the .data property
		 *  - detects (but doesn't yet handle) errors and warnings
		 *
		 * @param options object with these properties:
		 *  - endpoint: URL of the API endpoint to call
		 *  - callback: function to call when request completes
		 *  - body_data: (optional) string to pass into POST body
		 *  - arguments: (optional) key-value mapping of arguments to append to the URL
		 *  - warningCallback: (optional) function to call when API returns a warning
		 *  - errorCallback: (optional) function to call when API returns an error
		 */
		post: function(options) {
			// TODO: this code is duplicated above
			if (!options.arguments) { options.arguments = {}; }
			if (!options.body_data) { options.body_data = {}; }

			if (options.endpoint.substr(0, 1) !== '/') {
				options.endpoint = '/' + options.endpoint;
			}

			if (options.endpoint.indexOf('?') !== -1) {
				// parse out existing arguments if there are any in the endpoint string
				var query_string = options.endpoint.split('?');
				options.endpoint = query_string[0];

				query_string.split('&').forEach(function(arg) {
					arg_parts = arg.split('=');
					options.arguments[decodeURIComponent(arg_parts[0])] = decodeURIComponent(arg_parts[1]);
				});
			}

			// make sure ?format=json is included, then rebuild the query string
			options.arguments.format = "json";
			var arg_string = '';
			for (var key in options.arguments) {
				arg_string += encodeURIComponent(key) + '=' + encodeURIComponent(options.arguments[key]);
			}

			var request_finished = function(responseData) {
				if (responseData.errors && responseData.errors.length > 0) {
					if (options.errorCallback) {
						options.errorCallback.apply(this, arguments);
					}
				}
				if (responseData.warnings && responseData.warnings.length > 0) {
					if (options.warningCallback) {
						options.warningCallback.apply(this, arguments);
					}
				}

				if (responseData.success) {
					var modified_args = arguments;
					modified_args[0] = responseData;
					options.callback.apply(this, modified_args);
				}
			}

			$.ajax({
				type: "POST",
				url: options.endpoint + '?' + arg_string,
				data: options.body_data,
				error: pm.data.xhrErrorHandler(options),
				success: request_finished,
				dataType: "json"
			});
		},

		/**
		 * Convenience wrapper around pm.data.api for simple API calls that just
		 * need their output directly rendered with a particular Handlebars template.
		 *
		 * @param options object with these properties:
		 *  - endpoint: URL of the API endpoint to call
		 *  - template: compiled Handlebars template to use
		 *  - element: element in which to render the template
		 *  - callback: optional callback to run after rendering
		 */
		template_from_api: function(options) {
			pm.data.api({
				endpoint: options.endpoint,
				callback: function(data) {
					$(options.element).html(
						options.template.apply(this, [data])
					);

					if (options.callback) {
						options.callback.apply(this, [data]);
					}
				}
			});
		},

		/**
		 * Wrapper for making multiple requests to pm.data.api in sequence (i.e. one by one),
		 * then optionally calling a final callback when everything is finished.
		 *
		 * @param options object with these properties:
		 * - requests array of objects, each having an "endpoint" property and a "callback"
		 *            property; each endpoint is requested in array order, then the
		 *            corresponding callback is run (as if it had been sent to pm.data.api)
		 * - final_callback optional callback to run after all requests have completed
		 */
		sequential_get: function(options) {
			if (!options.requests || !options.requests.length) {
				if (options.final_callback) {
					options.final_callback.apply(this, []);
				}
				return false;
			}

			var request_index = 0;

			var get_next = function(request) {
					pm.data.api({
						endpoint: request.endpoint,
						callback: function() {
							request.callback.apply(this, arguments);

							request_index ++;
							if (options.requests[request_index]) {
								get_next(options.requests[request_index]);
							} else {
								if (options.final_callback) {
									options.final_callback.apply(this, []);
								}
							}
						}
					});
			}

			get_next(options.requests[request_index]);
		},

		/**
		 * Convenience wrapper to get the parent objects of an application or version when
		 * showing a detail view in the interface (e.g. for menus and breadcrumbs).
		 *
		 * @param options object with these properties:
		 *  - application_id or version_id: database ID of the object to fetch parents for
		 *  - callback: function to run with output value as the only argument. Output value
		 *    is an object with properties workspace, application, and optionally version
		 *    each corresponding to the objects returned by the API.
		 */
		get_app_parents: function(options) {
			var retval = {};

			var version_received = function(data) {
				retval.version = data.version;
				get_application(data.version.application_id);
			}
			var application_received = function(data) {
				retval.application = data.application;
				get_workspace(data.application.workspace_id);
			}
			var workspace_received = function(data) {
				retval.workspace = data.workspace;
				options.callback.apply(this, [retval]);
			}

			get_version = function(id) {
				pm.data.api({
					endpoint: 'version/' + id,
					callback: version_received
				});
			}
			get_application = function(id) {
				pm.data.api({
					endpoint: 'application/' + id,
					callback: application_received
				});
			}
			get_workspace = function(id) {
				pm.data.api({
					endpoint: 'workspace/' + id,
					callback: workspace_received
				});
			}

			if (options.version_id) {
				get_version(options.version_id);
			} else if (options.application_id) {
				get_application(options.application_id);
			} else if (options.workspace_id) {
				get_workspace(options.workspace_id);
			}
		},

		// Preferred method for getting access to the socket.io connection object
		getSocket: function() {
			return streamSocket;
		},

		registerScreenTimeout: function(timeout) {
			screenTimeouts.push(timeout);
		},

		removeListeners: function() {
			if (pm.data.getSocket()) {
				pm.data.getSocket().removeAllListeners();
			}
			// Cancel all screen timeouts.
			for(var i = 0; i < screenTimeouts.length; i++) {
				clearTimeout(screenTimeouts[i]);
			}
			screenTimeouts = [];
		},

		emit: function() {
			// console.log('pm.data.emit:', arguments);
			pm.data.getSocket().emit.apply(streamSocket, arguments);
		},

		subscribe: function(eventName, callback, capture) {
			// console.log('pm.data.subscribe on ' + eventName);
			pm.data.getSocket().on(
				eventName,
				function() {
					callback.apply(streamSocket, arguments);
				},
				capture
			);
		},

		initSocket: function() {

			// Set up the web socket for getting stats, job, and other data
			// from the pacemaker (or long poll -- handled by socket.io)
			// TODO: This connection timeout is low to force it to fallback to XHR quickly
			// when websocket fails. This may be too short though for production use.
			// Maybe we can more intelligently decide this and give socket.io a better hint?
			streamSocket = new io.connect(window.location.protocol + '//' + window.location.host, {'connect timeout': 10000});
			streamSocket.on('disconnect', function() {
				streamSocket.socket.reconnect();
			});

		}

	};
}());
<commit_msg>Added an error message when the API request was unable to connect to the remote server (rather than giving an error on the console).<commit_after>/* Paasmaker - Platform as a Service
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * pm.data.js - wrapper to manage Web Sockets and JSON API calls from frontend code
 */

if (!window.pm) { var pm = {}; }	// TODO: module handling

// For short-term compatibility, keep this as a global object.
// Later we can make it private and require the use of getSocket.
var streamSocket;

pm.data = (function() {

	var screenTimeouts = [];

	return {
		/**
		 * Generic error handler for ajax requests sent via pm.data: try to parse the response
		 * body and show a Bootstrap alert box at the top of the page with the errors array.
		 * TODO: fails for non-JSON responses, and assumes only one .loading-overlay at a time
		 *
		 * @param options argument object from pm.data.api or pm.data.post
		 */
		xhrErrorHandler: function(options) {
			return function(result) {
				$('.loading-overlay').remove();
				if (result.responseText == '') {
					// Couldn't even connect.
					var responseData = {errors: ['Unable to connect to the remote server.']}
				} else {
					var responseData = JSON.parse(result.responseText);
					if (responseData.data.input_errors) {
						for(var key in responseData.data.input_errors) {
							responseData.errors.push(key + ": " + responseData.data.input_errors[key]);
						}
					}
				}
				if ($('#main_right_view').length) {
					$('#main_right_view').prepend(Handlebars.templates.error_block(responseData));
				} else {
					$('#main').prepend(Handlebars.templates.error_block(responseData));
				}

				if (options.errorCallback) {
					options.errorCallback.apply(this, arguments);
				}
			}
		},

		/**
		 * Wrapper for GET calls to the pacemaker's JSON API:
		 *  - appends ?format=json so other code doesn't need to
		 *  - parses JSON and gets out the .data property
		 *  - detects (but doesn't yet handle) errors and warnings
		 *
		 * @param options object with these properties:
		 *  - endpoint: URL of the API endpoint to call
		 *  - callback: function to call when request completes
		 *  - arguments: (optional) key-value mapping of arguments to append to the URL
		 *  - warningCallback: (optional) function to call when API returns a warning
		 *  - errorCallback: (optional) function to call when API returns an error
		 */
		api: function(options) {
			if (!options.arguments) { options.arguments = {}; }

			if (options.endpoint.substr(0, 1) !== '/') {
				options.endpoint = '/' + options.endpoint;
			}

			if (options.endpoint.indexOf('?') !== -1) {
				// parse out existing arguments if there are any in the endpoint string
				var query_string = options.endpoint.split('?');
				options.endpoint = query_string[0];

				query_string[1].split('&').forEach(function(arg) {
					arg_parts = arg.split('=');
					options.arguments[decodeURIComponent(arg_parts[0])] = decodeURIComponent(arg_parts[1]);
				});
			}

			// make sure ?format=json is included, then rebuild the query string
			options.arguments.format = "json";
			var arg_string = '';
			for (var key in options.arguments) {
				if (arg_string != '') { arg_string += '&'; }
				arg_string += encodeURIComponent(key) + '=' + encodeURIComponent(options.arguments[key]);
			}

			$.ajax({
				dataType: "json",
				url: options.endpoint + '?' + arg_string,
				error: pm.data.xhrErrorHandler(options),
				success: function(responseData) {
					if (responseData.errors && responseData.errors.length > 0) {
						if (options.errorCallback) {
							options.errorCallback.apply(this, arguments);
						}
					}
					if (responseData.warnings && responseData.warnings.length > 0) {
						if (options.warningCallback) {
							options.warningCallback.apply(this, arguments);
						}
					}

					// make sure other arguments are passed unchanged
					var modified_args = arguments;
					modified_args[0] = responseData.data;

					options.callback.apply(this, modified_args);
				}
			});
		},

		/**
		 * Wrapper for POST calls to the pacemaker's JSON API:
		 *  - appends ?format=json so other code doesn't need to
		 *  - parses JSON and gets out the .data property
		 *  - detects (but doesn't yet handle) errors and warnings
		 *
		 * @param options object with these properties:
		 *  - endpoint: URL of the API endpoint to call
		 *  - callback: function to call when request completes
		 *  - body_data: (optional) string to pass into POST body
		 *  - arguments: (optional) key-value mapping of arguments to append to the URL
		 *  - warningCallback: (optional) function to call when API returns a warning
		 *  - errorCallback: (optional) function to call when API returns an error
		 */
		post: function(options) {
			// TODO: this code is duplicated above
			if (!options.arguments) { options.arguments = {}; }
			if (!options.body_data) { options.body_data = {}; }

			if (options.endpoint.substr(0, 1) !== '/') {
				options.endpoint = '/' + options.endpoint;
			}

			if (options.endpoint.indexOf('?') !== -1) {
				// parse out existing arguments if there are any in the endpoint string
				var query_string = options.endpoint.split('?');
				options.endpoint = query_string[0];

				query_string.split('&').forEach(function(arg) {
					arg_parts = arg.split('=');
					options.arguments[decodeURIComponent(arg_parts[0])] = decodeURIComponent(arg_parts[1]);
				});
			}

			// make sure ?format=json is included, then rebuild the query string
			options.arguments.format = "json";
			var arg_string = '';
			for (var key in options.arguments) {
				arg_string += encodeURIComponent(key) + '=' + encodeURIComponent(options.arguments[key]);
			}

			var request_finished = function(responseData) {
				if (responseData.errors && responseData.errors.length > 0) {
					if (options.errorCallback) {
						options.errorCallback.apply(this, arguments);
					}
				}
				if (responseData.warnings && responseData.warnings.length > 0) {
					if (options.warningCallback) {
						options.warningCallback.apply(this, arguments);
					}
				}

				if (responseData.success) {
					var modified_args = arguments;
					modified_args[0] = responseData;
					options.callback.apply(this, modified_args);
				}
			}

			$.ajax({
				type: "POST",
				url: options.endpoint + '?' + arg_string,
				data: options.body_data,
				error: pm.data.xhrErrorHandler(options),
				success: request_finished,
				dataType: "json"
			});
		},

		/**
		 * Convenience wrapper around pm.data.api for simple API calls that just
		 * need their output directly rendered with a particular Handlebars template.
		 *
		 * @param options object with these properties:
		 *  - endpoint: URL of the API endpoint to call
		 *  - template: compiled Handlebars template to use
		 *  - element: element in which to render the template
		 *  - callback: optional callback to run after rendering
		 */
		template_from_api: function(options) {
			pm.data.api({
				endpoint: options.endpoint,
				callback: function(data) {
					$(options.element).html(
						options.template.apply(this, [data])
					);

					if (options.callback) {
						options.callback.apply(this, [data]);
					}
				}
			});
		},

		/**
		 * Wrapper for making multiple requests to pm.data.api in sequence (i.e. one by one),
		 * then optionally calling a final callback when everything is finished.
		 *
		 * @param options object with these properties:
		 * - requests array of objects, each having an "endpoint" property and a "callback"
		 *            property; each endpoint is requested in array order, then the
		 *            corresponding callback is run (as if it had been sent to pm.data.api)
		 * - final_callback optional callback to run after all requests have completed
		 */
		sequential_get: function(options) {
			if (!options.requests || !options.requests.length) {
				if (options.final_callback) {
					options.final_callback.apply(this, []);
				}
				return false;
			}

			var request_index = 0;

			var get_next = function(request) {
					pm.data.api({
						endpoint: request.endpoint,
						callback: function() {
							request.callback.apply(this, arguments);

							request_index ++;
							if (options.requests[request_index]) {
								get_next(options.requests[request_index]);
							} else {
								if (options.final_callback) {
									options.final_callback.apply(this, []);
								}
							}
						}
					});
			}

			get_next(options.requests[request_index]);
		},

		/**
		 * Convenience wrapper to get the parent objects of an application or version when
		 * showing a detail view in the interface (e.g. for menus and breadcrumbs).
		 *
		 * @param options object with these properties:
		 *  - application_id or version_id: database ID of the object to fetch parents for
		 *  - callback: function to run with output value as the only argument. Output value
		 *    is an object with properties workspace, application, and optionally version
		 *    each corresponding to the objects returned by the API.
		 */
		get_app_parents: function(options) {
			var retval = {};

			var version_received = function(data) {
				retval.version = data.version;
				get_application(data.version.application_id);
			}
			var application_received = function(data) {
				retval.application = data.application;
				get_workspace(data.application.workspace_id);
			}
			var workspace_received = function(data) {
				retval.workspace = data.workspace;
				options.callback.apply(this, [retval]);
			}

			get_version = function(id) {
				pm.data.api({
					endpoint: 'version/' + id,
					callback: version_received
				});
			}
			get_application = function(id) {
				pm.data.api({
					endpoint: 'application/' + id,
					callback: application_received
				});
			}
			get_workspace = function(id) {
				pm.data.api({
					endpoint: 'workspace/' + id,
					callback: workspace_received
				});
			}

			if (options.version_id) {
				get_version(options.version_id);
			} else if (options.application_id) {
				get_application(options.application_id);
			} else if (options.workspace_id) {
				get_workspace(options.workspace_id);
			}
		},

		// Preferred method for getting access to the socket.io connection object
		getSocket: function() {
			return streamSocket;
		},

		registerScreenTimeout: function(timeout) {
			screenTimeouts.push(timeout);
		},

		removeListeners: function() {
			if (pm.data.getSocket()) {
				pm.data.getSocket().removeAllListeners();
			}
			// Cancel all screen timeouts.
			for(var i = 0; i < screenTimeouts.length; i++) {
				clearTimeout(screenTimeouts[i]);
			}
			screenTimeouts = [];
		},

		emit: function() {
			// console.log('pm.data.emit:', arguments);
			pm.data.getSocket().emit.apply(streamSocket, arguments);
		},

		subscribe: function(eventName, callback, capture) {
			// console.log('pm.data.subscribe on ' + eventName);
			pm.data.getSocket().on(
				eventName,
				function() {
					callback.apply(streamSocket, arguments);
				},
				capture
			);
		},

		initSocket: function() {

			// Set up the web socket for getting stats, job, and other data
			// from the pacemaker (or long poll -- handled by socket.io)
			// TODO: This connection timeout is low to force it to fallback to XHR quickly
			// when websocket fails. This may be too short though for production use.
			// Maybe we can more intelligently decide this and give socket.io a better hint?
			streamSocket = new io.connect(window.location.protocol + '//' + window.location.host, {'connect timeout': 10000});
			streamSocket.on('disconnect', function() {
				streamSocket.socket.reconnect();
			});

		}

	};
}());
<|endoftext|>