<commit_before>define([
	'jquery',
	'components/slider/DeckSlider',
	'components/InlineEditor',
	'components/FlipMode',
	'models/Deck',
	'views/CardForm',
	'utils/utils'
], function(
	$,
	DeckSlider,
	InlineEditor,
	FlipMode,
	Deck,
	CardForm,
	utils
) {

function initModule() {
	var deck_slider = new DeckSlider($(".slider").first());
	var card_counter = {
		el: $("#counter"),
		slider: deck_slider,
		update: function() {
			var current = this.slider.getCurrentCardNum();
			var total = this.slider.getNumItems();
			this.el.html(current + " / " + total);
		}
	};
	var $cardDetail = $("#singleCardHolder");

	card_counter.update = $.proxy(card_counter.update, card_counter);

	deck_slider.bind("beforeslide", function(slider, data){
		var $controls = $cardDetail.find(".controls[data-card-id]");
		var $card = $cardDetail.find(".card[data-card-id]");

		$controls.removeClass("card-active").hide();
		$card.removeClass('card-active').hide();
		$card.find("audio").trigger("pause");

		deck_slider._slideDirection = (data.toIndex >= data.fromIndex ? "right" : "left");
		deck_slider._slideCurrent = (data.toIndex == data.fromIndex);
	});
	deck_slider.bind("slide", function(slider, data) {
		// find the card elements
		var $controls = $cardDetail.find(".controls[data-card-id="+data.card_id+"]");
		var $card = $cardDetail.find(".card[data-card-id="+data.card_id+"]");

		// this function handles automatically playing the first audio file in the card
		var playMedia = function() {
			var $audio = $card.find("audio").first();
			if($audio.length == 1) {
				if($audio[0].readyState >= 3) {
					$audio.trigger("play");
				} else {
					$audio.bind("canplay canplaythrough", function() {
						this.play();
					});
				}
			}
		};
		var slideOpts = {
			direction: deck_slider._slideDirection,
			complete: playMedia
		};

		// show the card controls
		$controls.addClass('card-active');
		$controls.show();

		// show the card
		$card.addClass('card-active');
		if(deck_slider._slideCurrent) {
			$card.show();
			playMedia();
		} else {
			$card.show('slide', slideOpts, 500);
		}
	});
	deck_slider.bind("load", function(slider, card_ids) {
		MODULE.loadCardMedia(card_ids);
	});

	deck_slider.bind("slide", card_counter.update);
	deck_slider.triggerLoad(0);
	deck_slider.goToCurrent();
	card_counter.update();

	$('#review_mode').click(function(){
	    location.href=this.href+'?card_id='+deck_slider.getCurrentCardId();
	    return false;
	});
	$('#quiz_mode').click(function(){
	    location.href=this.href+'&card_id='+deck_slider.getCurrentCardId();
	    return false;
	});

	$('#addCard').click(function(){
	    $('#cardFormContainer').toggle();
	    $('#singleCardHolder').toggle();
	    return false;
	});

	$('#card_cancel').click(function(){
	    $('#cardFormContainer').hide();
	    $('#singleCardHolder').show();
	    return false;
	});

	$('#card_create').click(function(){
	        $('#cardForm').submit();
	        return false;
	});

	$('.reveal').click(function() {
		MODULE.revealCard($(this), $(this).parent().next());
		return false;
	});

	$('#full_screen').click(function() {
	    var txt1 = 'Full Screen';
	    var txt2 = 'Exit Full Screen';
	    if ($("#full_screen").text() == txt1){
	        $("#wrapper").children().not("#singleCardHolder, .fullcardBtns").hide();
	        $("#appTitle, #navigation").hide();
	        $("#singleCardHolder").css('margin-left','8.5%');
	        $("#full_screen .control-text").text(txt2);
			$("#full").addClass("fa-compress").removeClass("fa-expand");
	    }
	    else{
	        $("#wrapper").children().not("#cardFormContainer, .fullcardBtns").show();
			$("#appTitle, #navigation").show();
	        $("#singleCardHolder").css('margin-left','0');
	        $("#full_screen .control-text").text(txt1);
			$("#full").addClass("fa-expand").removeClass("fa-compress");
	    }
	    return false;
	});

	// http://standardofnorms.wordpress.com/2012/04/08/shuffling-all-the-children-of-a-parent-element-in-javascript/
	$('#shuffle_cards').click(function() {
		deck_slider.shuffle();
		deck_slider.goToFirst();
	    return false;
	});

	$('#play_cards').click(function(){
	    var playText = 'Play';
	    var pauseText = 'Pause';
	    if ($("#play_cards .control-text").text() == playText){
			deck_slider.play(function() {
				$("#play_cards").click();
			});
	        $("#play_cards .control-text").text(pauseText);
			$("#play").removeClass('fa-play').addClass('fa-pause');
	    }
	    else{
			deck_slider.pause();
	        $("#play_cards .control-text").text(playText);
			$("#play").removeClass('fa-pause').addClass('fa-play');
	    }
	    return false;
	});

	utils.setupConfirm();
	this.setupFlipMode();
	this.setupEditableDeckTitle();
	this.setupKeyboardShortcuts();

	// fixes the reorganization of slider
	$("#initDeck").css("display","none");
	$("#holder").css("display","block");
};

	var MODULE = {
		initModule:initModule,
		// This function initializes flip mode
		setupFlipMode: function() {
			var flipMode = new FlipMode({btnEl: "#flip_mode"});
			return flipMode;
		},
		// This function adds the ability to inline edit elements with data-editable=yes.
		//
		// Assumes the element also has these data attributes:
		//		- data-editable-url: API endpoint URL
		//		- data-editable-field: name of the field to POST
		setupEditableDeckTitle: function() {
			$("[data-editable]").each(function(index, el) {
				var $el = $(el);
				var editable = $el.data('editable') || 'no';
				var deck_id = $el.data('editable-id') || '';
				if(editable !== 'yes') {
					return;
				}

				var editor = new InlineEditor($el, {
					edit: function(editor, value, settings) {
						var deck = new Deck({ id: deck_id });
						var result = deck.rename(value);
						editor.value = value;
						return result;
					},
					success: function(data, textStatus, xhr) {
						var success = data.success;
						var value = editor.value;
						if(success === true) {
							this.highlight({color: 'yellow'});
							$("#navigation").find("[data-deck-id='"+deck_id+"']").text(value);
						} else if(success === false) {
							this.highlight({color: 'false'});
						}
						if(!success) {
							window.alert("Error saving: "+ data.errors[field]);
						}
						return success;
					},
					error: function(xhr, textStatus, errorThrown) {
						window.alert("Error saving: "+ errorThrown);
					}
				});
			});
		},
		setupKeyboardShortcuts: function() {
			$(document).on('keydown', function(e) {
				MODULE.onKeyDownRevealCard(e.keyCode, function(state) {
					$button_el = $('#allCards > .card-active .reveal');
					$reveal_content_el = $button_el.parent().next();
					MODULE.revealCard($button_el, $reveal_content_el, state);
				});
			});
		},
		onKeyDownRevealCard: function(keyCode, callback) {
			// reveal content when down arrow is pressed
			if(keyCode == 40) {
				callback(true);
			} else if(keyCode == 38) {
				callback(false);
			}
		},
		revealCard: function(buttonEl, revealContentEl, state) {
			var $button_el = $(buttonEl);
			var $reveal_content_el = $(revealContentEl);
			var button_text = ['Reveal', 'Hide'];
			var css_cls = ['show', 'hide'];

			// if no state is explicitly passed (true=reveal, false=hide),
			// then assume we want to toggle the card
			if(typeof state === 'undefined') {
				state = !$reveal_content_el.hasClass('show');
			} else {
				state = !!state; // force boolean
			}

			if(state) {
				css_cls.reverse();
				button_text.reverse();
			}

			$reveal_content_el.removeClass(css_cls[0]).addClass(css_cls[1]);
			$button_el.text(button_text[0]);

			return state;
		},
		loadCardMedia: function(card_ids) {
			var loadedCls = 'cardFieldMediaLoaded';
			var loadMedia = {
				'A': function(data) {
					var html = '<audio alt="'+data.label+'" controls="controls" class="cardFieldAudio" preload="auto"><source src="'+data.src+'" />Your browser does not support the audio element.</audio>';
					return html;
				},
				'V': function(data) {
					var html = '<video alt="'+data.label+'" src="'+data.src+'" class="cardFieldVideo" controls>Your browser does not support the video element.</video>';
					return html;
				},
				'I': function(data) {
					var html = '';
					return html;
				}
			};
			$.each(card_ids, function(idx, card_id) {
				$('.card[data-card-id="'+card_id+'"] .cardFieldMedia').filter(function() {
					return !$(this).hasClass(loadedCls);
				}).each(function(idx, el) {
					var type = $(el).data("type");
					var params = {
						"src": $(el).data("src"),
						"label": $(el).data("label")
					};
					var result = loadMedia[type](params);
					$(el).addClass(loadedCls).append(result);
				});
			});
		}
	};

	return MODULE;
});
<commit_msg>Removed audio autoplay from this branch because it is buggy and out of scope.<commit_after>define([
	'jquery',
	'components/slider/DeckSlider',
	'components/InlineEditor',
	'components/FlipMode',
	'models/Deck',
	'views/CardForm',
	'utils/utils'
], function(
	$,
	DeckSlider,
	InlineEditor,
	FlipMode,
	Deck,
	CardForm,
	utils
) {

function initModule() {
	var deck_slider = new DeckSlider($(".slider").first());
	var card_counter = {
		el: $("#counter"),
		slider: deck_slider,
		update: function() {
			var current = this.slider.getCurrentCardNum();
			var total = this.slider.getNumItems();
			this.el.html(current + " / " + total);
		}
	};
	var $cardDetail = $("#singleCardHolder");

	card_counter.update = $.proxy(card_counter.update, card_counter);

	deck_slider.bind("beforeslide", function(slider, data){
		var $controls = $cardDetail.find(".controls[data-card-id]");
		var $card = $cardDetail.find(".card[data-card-id]");

		$controls.removeClass("card-active").hide();
		$card.removeClass('card-active').hide();

		deck_slider._slideDirection = (data.toIndex >= data.fromIndex ? "right" : "left");
		deck_slider._slideCurrent = (data.toIndex == data.fromIndex);
	});
	deck_slider.bind("slide", function(slider, data) {
		// find the card elements
		var $controls = $cardDetail.find(".controls[data-card-id="+data.card_id+"]");
		var $card = $cardDetail.find(".card[data-card-id="+data.card_id+"]");

		var slideOpts = {
			direction: deck_slider._slideDirection
		};

		// show the card controls
		$controls.addClass('card-active');
		$controls.show();

		// show the card
		$card.addClass('card-active');
		if(deck_slider._slideCurrent) {
			$card.show();
		} else {
			$card.show('slide', slideOpts, 500);
		}
	});
	deck_slider.bind("load", function(slider, card_ids) {
		MODULE.loadCardMedia(card_ids);
	});

	deck_slider.bind("slide", card_counter.update);
	deck_slider.triggerLoad(0);
	deck_slider.goToCurrent();
	card_counter.update();

	$('#review_mode').click(function(){
	    location.href=this.href+'?card_id='+deck_slider.getCurrentCardId();
	    return false;
	});
	$('#quiz_mode').click(function(){
	    location.href=this.href+'&card_id='+deck_slider.getCurrentCardId();
	    return false;
	});

	$('#addCard').click(function(){
	    $('#cardFormContainer').toggle();
	    $('#singleCardHolder').toggle();
	    return false;
	});

	$('#card_cancel').click(function(){
	    $('#cardFormContainer').hide();
	    $('#singleCardHolder').show();
	    return false;
	});

	$('#card_create').click(function(){
	        $('#cardForm').submit();
	        return false;
	});

	$('.reveal').click(function() {
		MODULE.revealCard($(this), $(this).parent().next());
		return false;
	});

	$('#full_screen').click(function() {
	    var txt1 = 'Full Screen';
	    var txt2 = 'Exit Full Screen';
	    if ($("#full_screen").text() == txt1){
	        $("#wrapper").children().not("#singleCardHolder, .fullcardBtns").hide();
	        $("#appTitle, #navigation").hide();
	        $("#singleCardHolder").css('margin-left','8.5%');
	        $("#full_screen .control-text").text(txt2);
			$("#full").addClass("fa-compress").removeClass("fa-expand");
	    }
	    else{
	        $("#wrapper").children().not("#cardFormContainer, .fullcardBtns").show();
			$("#appTitle, #navigation").show();
	        $("#singleCardHolder").css('margin-left','0');
	        $("#full_screen .control-text").text(txt1);
			$("#full").addClass("fa-expand").removeClass("fa-compress");
	    }
	    return false;
	});

	// http://standardofnorms.wordpress.com/2012/04/08/shuffling-all-the-children-of-a-parent-element-in-javascript/
	$('#shuffle_cards').click(function() {
		deck_slider.shuffle();
		deck_slider.goToFirst();
	    return false;
	});

	$('#play_cards').click(function(){
	    var playText = 'Play';
	    var pauseText = 'Pause';
	    if ($("#play_cards .control-text").text() == playText){
			deck_slider.play(function() {
				$("#play_cards").click();
			});
	        $("#play_cards .control-text").text(pauseText);
			$("#play").removeClass('fa-play').addClass('fa-pause');
	    }
	    else{
			deck_slider.pause();
	        $("#play_cards .control-text").text(playText);
			$("#play").removeClass('fa-pause').addClass('fa-play');
	    }
	    return false;
	});

	utils.setupConfirm();
	this.setupFlipMode();
	this.setupEditableDeckTitle();
	this.setupKeyboardShortcuts();

	// fixes the reorganization of slider
	$("#initDeck").css("display","none");
	$("#holder").css("display","block");
};

	var MODULE = {
		initModule:initModule,
		// This function initializes flip mode
		setupFlipMode: function() {
			var flipMode = new FlipMode({btnEl: "#flip_mode"});
			return flipMode;
		},
		// This function adds the ability to inline edit elements with data-editable=yes.
		//
		// Assumes the element also has these data attributes:
		//		- data-editable-url: API endpoint URL
		//		- data-editable-field: name of the field to POST
		setupEditableDeckTitle: function() {
			$("[data-editable]").each(function(index, el) {
				var $el = $(el);
				var editable = $el.data('editable') || 'no';
				var deck_id = $el.data('editable-id') || '';
				if(editable !== 'yes') {
					return;
				}

				var editor = new InlineEditor($el, {
					edit: function(editor, value, settings) {
						var deck = new Deck({ id: deck_id });
						var result = deck.rename(value);
						editor.value = value;
						return result;
					},
					success: function(data, textStatus, xhr) {
						var success = data.success;
						var value = editor.value;
						if(success === true) {
							this.highlight({color: 'yellow'});
							$("#navigation").find("[data-deck-id='"+deck_id+"']").text(value);
						} else if(success === false) {
							this.highlight({color: 'false'});
						}
						if(!success) {
							window.alert("Error saving: "+ data.errors[field]);
						}
						return success;
					},
					error: function(xhr, textStatus, errorThrown) {
						window.alert("Error saving: "+ errorThrown);
					}
				});
			});
		},
		setupKeyboardShortcuts: function() {
			$(document).on('keydown', function(e) {
				MODULE.onKeyDownRevealCard(e.keyCode, function(state) {
					$button_el = $('#allCards > .card-active .reveal');
					$reveal_content_el = $button_el.parent().next();
					MODULE.revealCard($button_el, $reveal_content_el, state);
				});
			});
		},
		onKeyDownRevealCard: function(keyCode, callback) {
			// reveal content when down arrow is pressed
			if(keyCode == 40) {
				callback(true);
			} else if(keyCode == 38) {
				callback(false);
			}
		},
		revealCard: function(buttonEl, revealContentEl, state) {
			var $button_el = $(buttonEl);
			var $reveal_content_el = $(revealContentEl);
			var button_text = ['Reveal', 'Hide'];
			var css_cls = ['show', 'hide'];

			// if no state is explicitly passed (true=reveal, false=hide),
			// then assume we want to toggle the card
			if(typeof state === 'undefined') {
				state = !$reveal_content_el.hasClass('show');
			} else {
				state = !!state; // force boolean
			}

			if(state) {
				css_cls.reverse();
				button_text.reverse();
			}

			$reveal_content_el.removeClass(css_cls[0]).addClass(css_cls[1]);
			$button_el.text(button_text[0]);

			return state;
		},
		loadCardMedia: function(card_ids) {
			var loadedCls = 'cardFieldMediaLoaded';
			var loadMedia = {
				'A': function(data) {
					var html = '<audio alt="'+data.label+'" controls="controls" class="cardFieldAudio" preload="auto"><source src="'+data.src+'" />Your browser does not support the audio element.</audio>';
					return html;
				},
				'V': function(data) {
					var html = '<video alt="'+data.label+'" src="'+data.src+'" class="cardFieldVideo" controls>Your browser does not support the video element.</video>';
					return html;
				},
				'I': function(data) {
					var html = '';
					return html;
				}
			};
			$.each(card_ids, function(idx, card_id) {
				$('.card[data-card-id="'+card_id+'"] .cardFieldMedia').filter(function() {
					return !$(this).hasClass(loadedCls);
				}).each(function(idx, el) {
					var type = $(el).data("type");
					var params = {
						"src": $(el).data("src"),
						"label": $(el).data("label")
					};
					var result = loadMedia[type](params);
					$(el).addClass(loadedCls).append(result);
				});
			});
		}
	};

	return MODULE;
});
<|endoftext|>


<commit_before>'use strict';

module.exports = Tile;

function Tile(data) {
    let frontImage = '';
    let backImage = '';

    let side = 'back'; //front, back
    let rotation = 0; //in degree, clockwise
    let height = 0.2;
    let radius = 5; //border to border
    let form = 'square'; //square, hexagonal

    let frontVisibleFor = null; //null for all, othervise array of allowed slotIndizes, empty array for no one
    let canBeMovedBy = [];
    let canBeRotatedBy = [];
    let canBeFlippedBy = [];

    this.setFrontImage = function(newFrontImage) {
        if (typeof newFrontImage === 'undefined') throw new Error('Tile_v1 must define "frontImage".');
        if (typeof newFrontImage !== 'string') throw new Error('Tile_v1.frontImage must be a string.');
        if (newFrontImage === '') throw new Error('Tile_v1.frontImage must be filled.');

        frontImage = newFrontImage
    };

    this.setBackImage = function(newBackImage) {
        if (typeof newBackImage === 'undefined') throw new Error('Tile_v1 must define "backImage".');
        if (typeof newBackImage !== 'string') throw new Error('Tile_v1.backImage must be a string.');
        if (newBackImage === '') throw new Error('Tile_v1.backImage must be filled.');

        backImage = newBackImage
    };

    this.setSide = function(newSide) {
        side = newSide;
    };

    this.setRotation = function(newRotation) {
        rotation = newRotation;
    };

    this.setHeight = function(newHeight) {
        height = newHeight;
    };

    this.setRadius = function(newRadius) {
        radius = newRadius
    };

    this.setForm = function(newForm) {
        form = newForm;
    };

    this.setFrontVisibleFor = function(newFrontVisibleFor) {
        frontVisibleFor = newFrontVisibleFor;
    };

    this.setCanBeMovedBy = function(newCanBeMovedBy) {
        canBeMovedBy = newCanBeMovedBy;
    };

    this.setCanBeRotatedBy = function(newCanBeRotatedBy) {
        canBeRotatedBy = newCanBeRotatedBy;
    };

    this.setCanBeFlippedBy = function(newCanBeFlippedBy) {
        canBeFlippedBy = newCanBeFlippedBy;
    };

    this.transformToFrontend = function(slotIndex) {
        return {
            frontImage: frontVisibleFor === null || frontVisibleFor.indexOf(slotIndex) > -1 ? frontImage : null,
            backImage: backImage,
            side: side,
            rotation: rotation,
            height: height,
            radius: radius,
            form: form,
            canBeMovedTo: this.getCanBeMovedTo(slotIndex),
            canBeRotatedTo: this.getCanBeRotatedTo(slotIndex),
            canBeFlipped: canBeFlippedBy.indexOf(slotIndex) > -1
        };
    };

    this.getCanBeMovedTo = function(slotIndex) {
        let moveTargets = [];

        for (let i = 0; i < canBeMovedBy.length; i++) {
            if (canBeMovedBy[i].slotIndex === slotIndex) {
                moveTargets.push(canBeMovedBy[i].target);
            }
        }

        return moveTargets;
    };

    this.getCanBeRotatedTo = function(slotIndex) {
        let possibleRotations = [];

        for (let i = 0; i < canBeRotatedBy.length; i++) {
            if (canBeRotatedBy[i].slotIndex === slotIndex) {
                possibleRotations.push(canBeRotatedBy[i].rotation);
            }
        }

        return possibleRotations;
    };

    this.onEvent = function(event, data) {
        if (event === 'tile.sideChanged') {
            side = data.side;

            return {
                side: side
            };
        }

        if (event === 'tile.rotationChanged') {
            rotation = data.rotation;

            return {
                rotation: rotation
            };
        }

        if (event === 'tile.permissionChanged') {
            frontVisibleFor = data.frontVisibleFor;
            canBeMovedBy = data.canBeMovedBy;
            canBeRotatedBy = data.canBeRotatedBy;
            canBeFlippedBy = data.canBeFlippedBy;

            return (slotIndex) => {
                return {
                    frontImage: frontVisibleFor === null || frontVisibleFor.indexOf(slotIndex) > -1 ? frontImage : null,
                    canBeMovedTo: this.getCanBeMovedTo(slotIndex),
                    canBeRotatedTo: this.getCanBeRotatedTo(slotIndex),
                    canBeFlipped: canBeFlippedBy.indexOf(slotIndex) > -1
                };
            };
        }

        return null;
    };

    this.onMethod = function(method, data, slotIndex) {
        if (method === 'tile.flip') {
            if (canBeFlippedBy.indexOf(slotIndex) === -1) {
                throw new MethodError('tile.notAllowedToFlip');
            }

            return {};
        }

        if (method === 'tile.rotate') {
            if (!canBeRotated) {
                throw new MethodError('tile.notAllowedToRotate');
            }

            return {
                rotation: data.rotation
            };
        }

        if (method === 'tile.move') {
            let validMove = false;
            for (let i = 0; i < canBeMovedBy.length; i++) {
                if (canBeMovedBy[i].slotIndex === slotIndex && canBeMovedBy[i].target.id === data.containerId && canBeMovedBy[i].target.data.x === data.x && canBeMovedBy[i].target.data.y === data.y) {
                    validMove = true;
                    break;
                }
            }
            if (!validMove) {
                throw new MethodError('tile.notAllowedToMove');
            }

            return {
                containerId: data.containerId,
                x: data.x,
                y: data.y,
                index: data.index
            };
        }

        throw new MethodError('method.notFound');
    };

    this.setFrontImage(data.frontImage);
    this.setBackImage(data.backImage);
    this.setSide(data.side);
    this.setRotation(data.rotation);
    this.setHeight(data.height);
    this.setRadius(data.radius);
    this.setForm(data.form);
    this.setFrontVisibleFor(data.frontVisibleFor);
    this.setCanBeMovedBy(data.canBeMovedBy);
    this.setCanBeRotatedBy(data.canBeRotatedBy);
    this.setCanBeFlippedBy(data.canBeFlippedBy);
}<commit_msg>tile_v1: Bugfix rotation<commit_after>'use strict';

module.exports = Tile;

function Tile(data) {
    let frontImage = '';
    let backImage = '';

    let side = 'back'; //front, back
    let rotation = 0; //in degree, clockwise
    let height = 0.2;
    let radius = 5; //border to border
    let form = 'square'; //square, hexagonal

    let frontVisibleFor = null; //null for all, othervise array of allowed slotIndizes, empty array for no one
    let canBeMovedBy = [];
    let canBeRotatedBy = [];
    let canBeFlippedBy = [];

    this.setFrontImage = function(newFrontImage) {
        if (typeof newFrontImage === 'undefined') throw new Error('Tile_v1 must define "frontImage".');
        if (typeof newFrontImage !== 'string') throw new Error('Tile_v1.frontImage must be a string.');
        if (newFrontImage === '') throw new Error('Tile_v1.frontImage must be filled.');

        frontImage = newFrontImage
    };

    this.setBackImage = function(newBackImage) {
        if (typeof newBackImage === 'undefined') throw new Error('Tile_v1 must define "backImage".');
        if (typeof newBackImage !== 'string') throw new Error('Tile_v1.backImage must be a string.');
        if (newBackImage === '') throw new Error('Tile_v1.backImage must be filled.');

        backImage = newBackImage
    };

    this.setSide = function(newSide) {
        side = newSide;
    };

    this.setRotation = function(newRotation) {
        rotation = newRotation;
    };

    this.setHeight = function(newHeight) {
        height = newHeight;
    };

    this.setRadius = function(newRadius) {
        radius = newRadius
    };

    this.setForm = function(newForm) {
        form = newForm;
    };

    this.setFrontVisibleFor = function(newFrontVisibleFor) {
        frontVisibleFor = newFrontVisibleFor;
    };

    this.setCanBeMovedBy = function(newCanBeMovedBy) {
        canBeMovedBy = newCanBeMovedBy;
    };

    this.setCanBeRotatedBy = function(newCanBeRotatedBy) {
        canBeRotatedBy = newCanBeRotatedBy;
    };

    this.setCanBeFlippedBy = function(newCanBeFlippedBy) {
        canBeFlippedBy = newCanBeFlippedBy;
    };

    this.transformToFrontend = function(slotIndex) {
        return {
            frontImage: frontVisibleFor === null || frontVisibleFor.indexOf(slotIndex) > -1 ? frontImage : null,
            backImage: backImage,
            side: side,
            rotation: rotation,
            height: height,
            radius: radius,
            form: form,
            canBeMovedTo: this.getCanBeMovedTo(slotIndex),
            canBeRotatedTo: this.getCanBeRotatedTo(slotIndex),
            canBeFlipped: canBeFlippedBy.indexOf(slotIndex) > -1
        };
    };

    this.getCanBeMovedTo = function(slotIndex) {
        let moveTargets = [];

        for (let i = 0; i < canBeMovedBy.length; i++) {
            if (canBeMovedBy[i].slotIndex === slotIndex) {
                moveTargets.push(canBeMovedBy[i].target);
            }
        }

        return moveTargets;
    };

    this.getCanBeRotatedTo = function(slotIndex) {
        let possibleRotations = [];

        for (let i = 0; i < canBeRotatedBy.length; i++) {
            if (canBeRotatedBy[i].slotIndex === slotIndex) {
                possibleRotations.push(canBeRotatedBy[i].rotation);
            }
        }

        return possibleRotations;
    };

    this.onEvent = function(event, data) {
        if (event === 'tile.sideChanged') {
            side = data.side;

            return {
                side: side
            };
        }

        if (event === 'tile.rotationChanged') {
            rotation = data.rotation;

            return {
                rotation: rotation
            };
        }

        if (event === 'tile.permissionChanged') {
            frontVisibleFor = data.frontVisibleFor;
            canBeMovedBy = data.canBeMovedBy;
            canBeRotatedBy = data.canBeRotatedBy;
            canBeFlippedBy = data.canBeFlippedBy;

            return (slotIndex) => {
                return {
                    frontImage: frontVisibleFor === null || frontVisibleFor.indexOf(slotIndex) > -1 ? frontImage : null,
                    canBeMovedTo: this.getCanBeMovedTo(slotIndex),
                    canBeRotatedTo: this.getCanBeRotatedTo(slotIndex),
                    canBeFlipped: canBeFlippedBy.indexOf(slotIndex) > -1
                };
            };
        }

        return null;
    };

    this.onMethod = function(method, data, slotIndex) {
        if (method === 'tile.flip') {
            if (canBeFlippedBy.indexOf(slotIndex) === -1) {
                throw new MethodError('tile.notAllowedToFlip');
            }

            return {};
        }

        if (method === 'tile.rotate') {
            let found = false;
            for (let rotationArray of canBeRotatedBy) {
                if (rotationArray.slotIndex === slotIndex && rotationArray.rotation === data.rotation) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new MethodError('tile.notAllowedToRotate');
            }

            return {
                rotation: data.rotation
            };
        }

        if (method === 'tile.move') {
            let validMove = false;
            for (let i = 0; i < canBeMovedBy.length; i++) {
                if (canBeMovedBy[i].slotIndex === slotIndex && canBeMovedBy[i].target.id === data.containerId && canBeMovedBy[i].target.data.x === data.x && canBeMovedBy[i].target.data.y === data.y) {
                    validMove = true;
                    break;
                }
            }
            if (!validMove) {
                throw new MethodError('tile.notAllowedToMove');
            }

            return {
                containerId: data.containerId,
                x: data.x,
                y: data.y,
                index: data.index
            };
        }

        throw new MethodError('method.notFound');
    };

    this.setFrontImage(data.frontImage);
    this.setBackImage(data.backImage);
    this.setSide(data.side);
    this.setRotation(data.rotation);
    this.setHeight(data.height);
    this.setRadius(data.radius);
    this.setForm(data.form);
    this.setFrontVisibleFor(data.frontVisibleFor);
    this.setCanBeMovedBy(data.canBeMovedBy);
    this.setCanBeRotatedBy(data.canBeRotatedBy);
    this.setCanBeFlippedBy(data.canBeFlippedBy);
}<|endoftext|>


<commit_before>ig.module(
	'game.entities.wizard'
).requires(
	'game.entities.hunter',
	'game.core.upgrades',
	'game.entities.abstract.ranged-unit'
).defines(function () {

EntityWizard = RangedUnit.extend({
	animSheet: new ig.AnimationSheet('media/img/wizard.png', 8, 8),
	preview: {
		image: new ig.Image('media/img/wizard-preview.png'),
		offset: { x: 0, y: -10 }
	},
	EnemyType: EntityHunter,
	range: 6,
	speed: 1/2000,
	value: 100,
	power: 8,

	upgrades: [
		new Tower.RangeUpgrade(7, 100),
		new Tower.RangeUpgrade(8, 200),
		new Tower.PowerUpgrade(11, 60),
		new Tower.PowerUpgrade(14, 120),
		new Tower.SpeedUpgrade(1/1750, 70)
	],

	init: function (x, y, settings) {
		this.parent(x, y, settings);
		this.addAnim('down', 1, [0]);
		this.addAnim('downAtk', 0.5, [1, 0], true);
		this.addAnim('up', 1, [2]);
		this.addAnim('upAtk', 0.5, [3, 2], true);
		this.addAnim('right', 1, [4]);
		this.addAnim('rightAtk', 0.5, [5, 4], true);
		this.addAnim('left', 1, [6]);
		this.addAnim('leftAtk', 0.5, [7, 6], true);
	},

	update: function () {
		this.parent();
		var target = this.target();
		if (!target) return;
		var theta = Math.atan2(
			target.pos.y - this.pos.y,
			target.pos.x - this.pos.x
		).toDeg();
		while (theta < 0) theta += 360;
		while (theta > 360) theta -= 360;

		if (theta >= 45 && theta < 135) {
			this.currentAnim = this.anims.downAtk;
		}
		else if (theta >= 135 && theta < 225) {
			this.currentAnim = this.anims.leftAtk;
		}
		else if (theta >= 225 && theta < 315) {
			this.currentAnim = this.anims.upAtk;
		}
		else {
			this.currentAnim = this.anims.rightAtk;
		}
	},

	disengage: function (target) {
		if (this.currentAnim === this.anims.rightAtk) {
			this.currentAnim = this.anims.right;
		}
		else if (this.currentAnim === this.anims.leftAtk) {
			this.currentAnim === this.anims.left;
		}
		else if (this.currentAnim === this.anims.upAtk) {
			this.currentAnim = this.anims.up;
		}
		else {
			this.currentAnim = this.anims.down;
		}
	},

	fireAt: function (target) {
		target.receiveDamage(this.power, this);
		this.currentAnim.gotoFrame(0);
	}
});

});<commit_msg>Fixed bug in wizard where target was treated like a function, causing crash<commit_after>ig.module(
	'game.entities.wizard'
).requires(
	'game.entities.hunter',
	'game.core.upgrades',
	'game.entities.abstract.ranged-unit'
).defines(function () {

EntityWizard = RangedUnit.extend({
	animSheet: new ig.AnimationSheet('media/img/wizard.png', 8, 8),
	preview: {
		image: new ig.Image('media/img/wizard-preview.png'),
		offset: { x: 0, y: -10 }
	},
	EnemyType: EntityHunter,
	range: 6,
	speed: 1/2000,
	value: 100,
	power: 8,

	upgrades: [
		new Tower.RangeUpgrade(7, 100),
		new Tower.RangeUpgrade(8, 200),
		new Tower.PowerUpgrade(11, 60),
		new Tower.PowerUpgrade(14, 120),
		new Tower.SpeedUpgrade(1/1750, 70)
	],

	init: function (x, y, settings) {
		this.parent(x, y, settings);
		this.addAnim('down', 1, [0]);
		this.addAnim('downAtk', 0.5, [1, 0], true);
		this.addAnim('up', 1, [2]);
		this.addAnim('upAtk', 0.5, [3, 2], true);
		this.addAnim('right', 1, [4]);
		this.addAnim('rightAtk', 0.5, [5, 4], true);
		this.addAnim('left', 1, [6]);
		this.addAnim('leftAtk', 0.5, [7, 6], true);
	},

	update: function () {
		this.parent();
		var target = this.target;
		if (!target) return;
		var theta = Math.atan2(
			target.pos.y - this.pos.y,
			target.pos.x - this.pos.x
		).toDeg();
		while (theta < 0) theta += 360;
		while (theta > 360) theta -= 360;

		if (theta >= 45 && theta < 135) {
			this.currentAnim = this.anims.downAtk;
		}
		else if (theta >= 135 && theta < 225) {
			this.currentAnim = this.anims.leftAtk;
		}
		else if (theta >= 225 && theta < 315) {
			this.currentAnim = this.anims.upAtk;
		}
		else {
			this.currentAnim = this.anims.rightAtk;
		}
	},

	disengage: function (target) {
		if (this.currentAnim === this.anims.rightAtk) {
			this.currentAnim = this.anims.right;
		}
		else if (this.currentAnim === this.anims.leftAtk) {
			this.currentAnim === this.anims.left;
		}
		else if (this.currentAnim === this.anims.upAtk) {
			this.currentAnim = this.anims.up;
		}
		else {
			this.currentAnim = this.anims.down;
		}
	},

	fireAt: function (target) {
		target.receiveDamage(this.power, this);
		this.currentAnim.gotoFrame(0);
	}
});

});
<|endoftext|>


<commit_before>(function (GoL) {
  var CT = {
    Dead:  {},
    Alive: {}
  };

  function cellSymbolToObject(symbol) {
    switch (symbol) {
      case '.':
        return CT.Dead;
      case '*':
        return CT.Alive;
      default:
        throw new Error('Invalid argument: unknown cell symbol in zero generation: ' + symbol);
    }
  }

  GoL.Support = {
      CellTypes: CT
    , parseCanvas: function (canvas) {
        if (!canvas || !canvas[0] || !canvas[0].getContext) {
          throw new Error("Invalid argument: not a canvas element");
        }
        return canvas[0];
      }
    , parseCellGrid: function (layout) {
        var rows = layout.trim().split("\n")
          , grid
          , gridWidth
          , gridIsSquare;

        if (rows.length < 1) {
          throw new Error("Invalid argument: cell grid is too small");
        }

        grid = _.map(rows, function (row) { return _.map(row.split(''), cellSymbolToObject) });
        gridWidth = grid.length;
        gridIsSquare = _.all(grid, function (row) { return row.length === gridWidth });

        if (!gridIsSquare) {
          throw new Error('Invalid argument: cell grid is not a square');
        }

        return grid;
      }
  };
})(GameOfLife);
<commit_msg>Add our own error class for invalid arguments<commit_after>(function (GoL) {
  var CT = { Dead: {}, Alive: {} };

  function InvalidArgument(message) {
    this.prototype  = Error.prototype;
    this.name       = 'InvalidArgument';
    this.message    = message;
    this.toString   = function () {
      return this.name + ': ' + this.message;
    };
  };

  function cellSymbolToObject(symbol) {
    switch (symbol) {
      case '.':
        return CT.Dead;
      case '*':
        return CT.Alive;
      default:
        throw new InvalidArgument('Unknown cell symbol in zero generation: ' + symbol);
    }
  }

  GoL.Support = {
      CellTypes: CT
    , parseCanvas: function (canvas) {
        if (!canvas || !canvas[0] || !canvas[0].getContext) {
          throw new InvalidArgument("Not a canvas element");
        }
        return canvas[0];
      }
    , parseCellGrid: function (layout) {
        var rows = layout.trim().split("\n")
          , grid
          , gridWidth
          , gridIsSquare;

        if (rows.length < 1) {
          throw new InvalidArgument('Cell grid is too small');
        }

        grid = _.map(rows, function (row) { return _.map(row.split(''), cellSymbolToObject) });
        gridWidth = grid.length;
        gridIsSquare = _.all(grid, function (row) { return row.length === gridWidth });

        if (!gridIsSquare) {
          throw new InvalidArgument('Cell grid is not a square');
        }

        return grid;
      }
  };
})(GameOfLife);
<|endoftext|>


<commit_before>var path = require('path');
var batch = require('gulp-batch');

module.exports = init;

function init(options){
  options = defaultOptions(options);

  registerTask(options);
}

function defaultOptions(options){
  options = options || {};
  options.gulp = options.gulp || require('gulp');
  options.task = options.task || 'watch';
  options.dependencies = options.dependencies || ['default'];
  options.watch = options.watch || [];
  options.batch = options.batch || {};
  options.log = options.log || true;
  options.start = options.start || 'default';

  return options;
}

function registerTask(options){
  var gulp = options.gulp;

  gulp.task(options.task, options.dependencies, function(){
    gulp.watch(options.watch, batch(options.batch, function(events, done){
      events
        .on('data', function(data){
          if(options.log){
            console.log(data.type, path.basename(data.path));
          }
        })
        .on('end', function(){
          gulp.start(options.start, done);
        });
    }));
  });
}<commit_msg>Fix log option with ternary operator<commit_after>var path = require('path');
var batch = require('gulp-batch');

module.exports = init;

function init(options){
  options = defaultOptions(options);

  registerTask(options);
}

function defaultOptions(options){
  options = options || {};
  options.gulp = options.gulp || require('gulp');
  options.task = options.task || 'watch';
  options.dependencies = options.dependencies || ['default'];
  options.watch = options.watch || [];
  options.batch = options.batch || {};
  options.log = (typeof options.log !== 'undefined') ? options.log : true;
  options.start = options.start || 'default';

  return options;
}

function registerTask(options){
  var gulp = options.gulp;

  gulp.task(options.task, options.dependencies, function(){
    gulp.watch(options.watch, batch(options.batch, function(events, done){
      events
        .on('data', function(data){
          if(options.log){
            console.log(data.type, path.basename(data.path));
          }
        })
        .on('end', function(){
          gulp.start(options.start, done);
        });
    }));
  });
}<|endoftext|>


<commit_before>var _ = require('lodash');
var HookBase = require('../Base');
var HookMessage = require('../Message');
var AffianceError = require('../../error');

function PreCommitBase(config, context) {
  HookBase.prototype.constructor.apply(this, arguments);
}

Object.assign(PreCommitBase.prototype, HookBase.prototype);

var CONTEXT_DELEGATIONS = [
  'isAmendment',
  'isInitialCommit',
  'modifiedLinesInFile'
];
HookBase.delegateToContext(PreCommitBase, CONTEXT_DELEGATIONS);

// Extract file, line number, and type of message from a list of
// error and/or warning messages from the output of a pre-commit hook.
//
// Assumes each element of `outputMessages` is a separate error/warning
// with all the information needed to identify it.
//
// @param outputMessages [Array<String>] unprocessed output messages
// @param regex [RegExp] a regular expression that will capture the `file`
//   `line`, and `type` from each message.
// @param captureMap [Object] an object that maps the capture group
//   indexes to what it represents to work around javascripts lack of
//   named capture groups
// @option captureMap.file [Number] the index of the capture group
//   with the file name
// @option captureMap.line [Number] the index of the capture group
//   with the line number
// @option captureMap.type [Number] the index of the capture group
//   with the type number
// @param typeCategorizer [Function] a function to run against the
//   captured type to produce a standard type string ('error', 'warning')
//   If not provided, all messages will be considered errors
PreCommitBase.prototype.extractMessages = function(outputMessages, regex, captureMap, typeCategorizer) {
  if (!outputMessages || !outputMessages.length) { return []; }

  var self = this;
  return outputMessages.map(function(outputMessage, index) {
    // Reset lastIndex in case this is a global regex
    regex.lastIndex = 0;
    var matchResult = regex.exec(outputMessage);
    if(!matchResult) {
      throw new AffianceError.error(
        AffianceError.MessageProcessingError,
        'Unexpected output: unable to determine line number or type ' +
        'of error or warning for output:\n' +
        outputMessages.slice(index, -1).join("\n")
      );
    }

    var file = self.extractFile(matchResult, captureMap, outputMessage);
    var line = self.extractLine(matchResult, captureMap, outputMessage);
    var type = self.extractType(matchResult, captureMap, outputMessage, typeCategorizer);
    var content = self.extractContent(matchResult, captureMap, outputMessage);

    return new HookMessage(type, file, line, content || outputMessage);
  });
};

PreCommitBase.prototype.extractFile = function(matchResult, captureMap, outputMessage) {
  var capturedFile = matchResult[captureMap.file];
  if (_.isNil(capturedFile)) { return; }

  if (capturedFile === '') {
    throw new AffianceError.error(
      AffianceError.MessageProcessingError,
      'Unexpected output: no file found in ' + outputMessage
    );
  }

  return capturedFile
};

PreCommitBase.prototype.extractLine = function(matchResult, captureMap, outputMessage) {
  var capturedLine = matchResult[captureMap.line];
  if (_.isNil(capturedLine)) { return; }

  var lineNumber = parseInt(capturedLine, 10);

  if (_.isNaN(lineNumber)) {
    throw new AffianceError.error(
      AffianceError.MessageProcessingError,
      'Unexpected output: invalid line number found in ' + outputMessage
    );
  }
  return lineNumber
};

PreCommitBase.prototype.extractType = function(matchResult, captureMap, outputMessage, typeCategorizer) {
  if (!typeCategorizer) {
    // Assume the message is an error if there is no categorizer
    return 'error'
  }

  var capturedType = matchResult[captureMap.type] || null;
  var type = typeCategorizer(capturedType);
  if (HookMessage.MESSAGE_TYPES.indexOf(type) < 0) {
    throw new AffianceError.error(
      AffianceError.MessageProcessingError,
      ['Invalid message type', type, 'for', outputMessage + ':',
        'must be one of', HookMessage.MESSAGE_TYPES.join(', ')
      ].join(' ')
    );
  }

  return type;
};

PreCommitBase.prototype.extractContent = function(matchResult, captureMap, outputMessage) {
  if (_.isNil(captureMap.content)) { return; }

  var capturedContent = matchResult[captureMap.content];
  if (_.isNil(capturedContent)) { return; }

  return capturedContent.trim();
};

module.exports = PreCommitBase;
<commit_msg>Classify pre-commit base class<commit_after>'use strict';
const _ = require('lodash');
const HookBase = require('../Base');
const HookMessage = require('../Message');
const AffianceError = require('../../error');

/**
 * @class PreCommitBase
 * @extends HookBase
 * @classdesc Base hook for all pre-commit hooks
 */
class PreCommitBase extends HookBase {
  /**
   * @typedef CaptureMap
   * @property {number} file - the index of the capture group
   *   with the file name
   * @property {number} line - the index of the capture group
   *   with the line number
   * @property {type} type - the index of the capture group
   *   with the type string
   */

  /**
   * Extract file, line number, and type of message from a list of
   * error and/or warning messages from the output of a pre-commit hook.
   *
   * Assumes each element of `outputMessages` is a separate error/warning
   * with all the information needed to identify it.
   *
   * @param {string[]} outputMessages - unprocessed output messages
   * @param {RegExp} regex - a regular expression that will capture the `file`
   *   `line`, and `type` from each message.
   * @param {CapturMap} captureMap - the map of capture group label to
   *   index in the matches array
   * @param {function} typeCategorizer - A function to run against the
   *   captured type
   * @returns {HookMessage[]} an array of hook messages build from the
   *   extracted file, line, type and content
   */
  extractMessages(outputMessages, regex, captureMap, typeCategorizer) {
    if (!outputMessages || !outputMessages.length) { return []; }

    return outputMessages.map((outputMessage, index) => {
      // Reset lastIndex in case this is a global regex
      regex.lastIndex = 0;
      let matchResult = regex.exec(outputMessage);
      if(!matchResult) {
        throw new AffianceError.error(
          AffianceError.MessageProcessingError,
          'Unexpected output: unable to determine line number or type ' +
          'of error or warning for output:\n' +
          outputMessages.slice(index, -1).join('\n')
        );
      }

      let file = this.extractFile(matchResult, captureMap, outputMessage);
      let line = this.extractLine(matchResult, captureMap, outputMessage);
      let type = this.extractType(matchResult, captureMap, outputMessage, typeCategorizer);
      let content = this.extractContent(matchResult, captureMap, outputMessage);

      return new HookMessage(type, file, line, content);
    });
  }

  /**
   * Extract the file path from the matches array
   *
   * @param {Array} matchResult - the reseult of the RegExp#exec call
   * @param {CaptureMap} captureMap - the capture map to use to use on the matches array
   * @param {string} outputMessage - the ouput message in it's entirety
   * @returns {string}
   */
  extractFile(matchResult, captureMap, outputMessage) {
    let capturedFile = matchResult[captureMap.file];
    if (_.isNil(capturedFile)) { return; }

    if (capturedFile === '') {
      throw new AffianceError.error(
        AffianceError.MessageProcessingError,
        'Unexpected output: no file found in ' + outputMessage
      );
    }

    return capturedFile;
  }

  /**
   * Extract the line number from the matches array
   *
   * @param {Array} matchResult - the reseult of the RegExp#exec call
   * @param {CaptureMap} captureMap - the capture map to use to use on the matches array
   * @param {string} outputMessage - the ouput message in it's entirety
   * @returns {number}
   */
  extractLine(matchResult, captureMap, outputMessage) {
    let capturedLine = matchResult[captureMap.line];
    if (_.isNil(capturedLine)) { return; }

    let lineNumber = parseInt(capturedLine, 10);

    if (_.isNaN(lineNumber)) {
      throw new AffianceError.error(
        AffianceError.MessageProcessingError,
        'Unexpected output: invalid line number found in ' + outputMessage
      );
    }
    return lineNumber;
  }

  /**
   * Extract the line number from the matches array
   *
   * @param {Array} matchResult - the reseult of the RegExp#exec call
   * @param {CaptureMap} captureMap - the capture map to use to use on the matches array
   * @param {string} outputMessage - the ouput message in it's entirety
   * @returns {number}
   */
  extractType(matchResult, captureMap, outputMessage, typeCategorizer) {
    if (!typeCategorizer) {
      // Assume the message is an error if there is no categorizer
      return 'error';
    }

    let capturedType = matchResult[captureMap.type] || null;
    let type = typeCategorizer(capturedType);
    if (HookMessage.MESSAGE_TYPES.indexOf(type) < 0) {
      throw new AffianceError.error(
        AffianceError.MessageProcessingError,
        ['Invalid message type', type, 'for', outputMessage + ':',
          'must be one of', HookMessage.MESSAGE_TYPES.join(', ')
        ].join(' ')
      );
    }

    return type;
  }

  /**
   * Extract the content from the outputMessage
   *
   * @param {Array} matchResult - the reseult of the RegExp#exec call
   * @param {CaptureMap} captureMap - the capture map to use to use on the matches array
   * @param {string} outputMessage - the ouput message in it's entirety
   * @returns {number}
   */
  extractContent(matchResult, captureMap, outputMessage) {
    if (_.isNil(captureMap.content)) { return; }

    let capturedContent = matchResult[captureMap.content];
    if (_.isNil(capturedContent)) { return; }

    return capturedContent.trim() || outputMessage;
  }
}

const CONTEXT_DELEGATIONS = [
  'isAmendment',
  'isInitialCommit',
  'modifiedLinesInFile'
];
HookBase.delegateToContext(PreCommitBase, CONTEXT_DELEGATIONS);

module.exports = PreCommitBase;
<|endoftext|>


<commit_before>"use strict";

const EventEmitter = require("events");
const Promise = require("bluebird");
const uuid = require("uuid");
const murmur = require("murmurhash").v3;
const debug = require("debug");

//@OPTIONAL
let BlizzKafka = null;

const MESSAGE_TYPES = {
  PUBLISH: "-published",
  UNPUBLISH: "-unpublished",
  UPDATE: "-updated"
};

const DEFAULT_LOGGER = {
  debug: debug("sinek:nproducer:debug"),
  info: debug("sinek:nproducer:info"),
  warn: debug("sinek:nproducer:warn"),
  error: debug("sinek:nproducer:error")
};

/*
  BREAKING CHANGES (compared to connect/Producer):
  - send does not support arrays of messages
  - compressionType does not work anymore
  - no topics are passed in the constructor
  - send can now exactly write to a partition or with a specific key
  - there is an optional options object for the config named: noptions
  - there is an optional topic options object for the config named: tconf
  - sending now rejects if paused
  - you can now define a strict partition for send bufferFormat types
  - _lastProcessed is now null if no message has been send
  - closing will reset stats
  - tconf config field sets topic configuration
*/

class NProducer extends EventEmitter {

  constructor(config, _, defaultPartitionCount = 1) {
    super();

    try {
      if(!BlizzKafka){
        BlizzKafka = require("node-rdkafka");
      }
    } catch(error){
      throw new Error("You have to install node-rdkafka to use NProducer.");
    }

    if(!config){
      throw new Error("You are missing a config object.");
    }

    if(!config.logger || typeof config.logger !== "object"){
      config.logger = DEFAULT_LOGGER;
    }

    if(!config.options){
      config.options = {};
    }

    this.config = config;

    this.paused = false;
    this.producer = null;
    this._producerPollIntv = null;
    this.defaultPartitionCount = defaultPartitionCount;
    this._inClosing = false;

    this._totalSentMessages = 0;
    this._lastProcessed = null;
  }

  connect() {
    return new Promise((resolve, reject) => {

      let { zkConStr, kafkaHost, logger, options, noptions, tconf } = this.config;
      const { pollIntervalMs } = options;

      let conStr = null;

      if(typeof kafkaHost === "string"){
        conStr = kafkaHost;
      }

      if(typeof zkConStr === "string"){
        conStr = zkConStr;
      }

      if(conStr === null && !noptions){
        return reject(new Error("One of the following: zkConStr or kafkaHost must be defined."));
      }

      if(conStr === zkConStr){
        return reject(new Error("NProducer does not support zookeeper connection."));
      }

      const config = {
        "metadata.broker.list": conStr,
        "dr_cb": true
      }; //TODO transfer rest of config fields

      noptions = noptions || {};
      noptions = Object.assign({}, config, noptions);
      logger.debug(noptions);

      tconf = tconf ? tconf : {
        "request.required.acks": 1
      };
      logger.debug(tconf);

      this.producer = new BlizzKafka.Producer(noptions, tconf);

      this.producer.on("event.log", log => {
        logger.debug(log.message);
      });

      this.producer.on("event.error", error => {
        super.emit("error", error);
      });

      this.producer.on("error", error => {
        super.emit("error", error);
      });

      this.producer.on("delivery-report", (error, report) => {
        logger.info("DeliveryReport: " + JSON.stringify(report));
      });

      this.producer.on("disconnected", () => {
        if(this._inClosing){
          this._reset();
        }
        logger.warn("Disconnected.");
        //auto-reconnect??? -> handled by producer.poll()
      });

      this.producer.on("ready", () => {

        logger.info(`Native producer ready v. ${BlizzKafka.librdkafkaVersion}, e. ${BlizzKafka.features.join(", ")}.`);

        //poll broker for updates
        this._producerPollIntv = setInterval(() => {
          if(this.producer){
            this.producer.poll();
          }
        }, pollIntervalMs || 100);

        super.emit("ready");
      });

      logger.debug("Connecting..");
      this.producer.connect(null, (error, metadata) => {

        if(error){
          super.emit("error", error);
          return reject(error);
        }

        logger.debug(metadata);
        resolve();
      });
    });
  }

  _getPartitionForKey(key, partitionCount = 0){

    /*
    if(typeof key !== "string"){
      return Promise.reject("Key must be a valid string");
    } */

    if(partitionCount === 0){
      partitionCount = this.defaultPartitionCount;
    }

    return murmur(key) % partitionCount;
  }

  send(topicName, message, _partition = null, _key = null) {

    if (!this.producer) {
      return Promise.reject("producer is not yet setup.");
    }

    if(this.paused){
      return Promise.reject("producer is paused.");
    }

    if(!message || !(typeof message === "string" || Buffer.isBuffer(message))){
      return Promise.reject("message must be a string or an instance of Buffer.");
    }

    let partition = -1;
    if (this.defaultPartitionCount < 2) {
      partition = 0;
    } else {
      partition = NProducer._getRandomIntInclusive(0, this.defaultPartitionCount);
    }

    partition = _partition ? _partition : partition;
    const key = _key ? _key : uuid.v4();
    message = Buffer.isBuffer(message) ? message : new Buffer(message);

    this.config.logger.debug(JSON.stringify({topicName, partition, key}));
    const producedAt = Date.now();

    try {
      this.producer.produce(topicName, partition, message, key, producedAt);
      this._lastProcessed = producedAt;
      this._totalSentMessages++;
      return Promise.resolve({
        key,
        partition
      });
    } catch(error){
      return Promise.reject(error);
    }
  }

  buffer(topic, identifier, payload, partition = null, version = null) {

    if (typeof identifier === "undefined") {
      identifier = uuid.v4();
    }

    if (typeof identifier !== "string") {
      identifier = identifier + "";
    }

    if(typeof payload !== "object"){
      return Promise.reject("expecting payload to be of type object.");
    }

    if(typeof payload.id === "undefined"){
      payload.id = identifier;
    }

    if(version && typeof payload.version === "undefined"){
      payload.version = version;
    }

    partition = partition ? partition : this._getPartitionForKey(identifier);

    return this.send(topic, JSON.stringify(payload), partition, identifier);
  }

  _sendBufferFormat(topic, identifier, _payload, version = 1, _, partitionKey = null, partition = null, messageType = "") {

    if (typeof identifier === "undefined") {
      identifier = uuid.v4();
    }

    if (typeof identifier !== "string") {
      identifier = identifier + "";
    }

    if(typeof _payload !== "object"){
      return Promise.reject("expecting payload to be of type object.");
    }

    if(typeof _payload.id === "undefined"){
      _payload.id = identifier;
    }

    if(version && typeof _payload.version === "undefined"){
      _payload.version = version;
    }

    const payload = {
      payload: _payload,
      key: identifier,
      id: uuid.v4(),
      time: (new Date()).toISOString(),
      type: topic + messageType
    };

    partition = partition ? partition : this._getPartitionForKey(partitionKey ? partitionKey : identifier);
    return this.send(topic, JSON.stringify(payload), partition, identifier);
  }

  /**
   * alias
   */
  bufferFormat(topic, identifier, payload, version = 1, compressionType = 0, partitionKey = null) {
    this.bufferFormatPublish(topic, identifier, payload, version, compressionType, partitionKey);
  }

  bufferFormatPublish(topic, identifier, _payload, version = 1, _, partitionKey = null, partition = null){
    return this._sendBufferFormat(topic, identifier, _payload, version, _, partitionKey, partition, MESSAGE_TYPES.PUBLISH);
  }

  bufferFormatUpdate(topic, identifier, _payload, version = 1, _, partitionKey = null, partition = null){
    return this._sendBufferFormat(topic, identifier, _payload, version, _, partitionKey, partition, MESSAGE_TYPES.UPDATE);
  }

  bufferFormatUnpublish(topic, identifier, _payload, version = 1, _, partitionKey = null, partition = null){
    return this._sendBufferFormat(topic, identifier, _payload, version, _, partitionKey, partition, MESSAGE_TYPES.UNPUBLISH);
  }

  pause() {
    this.paused = true;
  }

  resume() {
    this.paused = false;
  }

  getStats() {
    return {
      totalPublished: this._totalSentMessages,
      last: this._lastProcessed,
      isPaused: this.paused
    };
  }

  refreshMetadata() {
    throw new Error("refreshMetadata is not available for NProducer.");
  }

  _reset(){
    this._lastProcessed = null;
    this._totalSentMessages = 0;
    this.paused = false;
    this._inClosing = false;
  }

  close() {

    if (this.producer) {
      this._inClosing = true;
      clearInterval(this._producerPollIntv);
      this.producer.disconnect();
      //this.producer = null;
    }
  }

  static _getRandomIntInclusive(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
}

module.exports = NProducer;
<commit_msg>Change delivery-report loglevel to debug<commit_after>"use strict";

const EventEmitter = require("events");
const Promise = require("bluebird");
const uuid = require("uuid");
const murmur = require("murmurhash").v3;
const debug = require("debug");

//@OPTIONAL
let BlizzKafka = null;

const MESSAGE_TYPES = {
  PUBLISH: "-published",
  UNPUBLISH: "-unpublished",
  UPDATE: "-updated"
};

const DEFAULT_LOGGER = {
  debug: debug("sinek:nproducer:debug"),
  info: debug("sinek:nproducer:info"),
  warn: debug("sinek:nproducer:warn"),
  error: debug("sinek:nproducer:error")
};

/*
  BREAKING CHANGES (compared to connect/Producer):
  - send does not support arrays of messages
  - compressionType does not work anymore
  - no topics are passed in the constructor
  - send can now exactly write to a partition or with a specific key
  - there is an optional options object for the config named: noptions
  - there is an optional topic options object for the config named: tconf
  - sending now rejects if paused
  - you can now define a strict partition for send bufferFormat types
  - _lastProcessed is now null if no message has been send
  - closing will reset stats
  - tconf config field sets topic configuration
*/

class NProducer extends EventEmitter {

  constructor(config, _, defaultPartitionCount = 1) {
    super();

    try {
      if(!BlizzKafka){
        BlizzKafka = require("node-rdkafka");
      }
    } catch(error){
      throw new Error("You have to install node-rdkafka to use NProducer.");
    }

    if(!config){
      throw new Error("You are missing a config object.");
    }

    if(!config.logger || typeof config.logger !== "object"){
      config.logger = DEFAULT_LOGGER;
    }

    if(!config.options){
      config.options = {};
    }

    this.config = config;

    this.paused = false;
    this.producer = null;
    this._producerPollIntv = null;
    this.defaultPartitionCount = defaultPartitionCount;
    this._inClosing = false;

    this._totalSentMessages = 0;
    this._lastProcessed = null;
  }

  connect() {
    return new Promise((resolve, reject) => {

      let { zkConStr, kafkaHost, logger, options, noptions, tconf } = this.config;
      const { pollIntervalMs } = options;

      let conStr = null;

      if(typeof kafkaHost === "string"){
        conStr = kafkaHost;
      }

      if(typeof zkConStr === "string"){
        conStr = zkConStr;
      }

      if(conStr === null && !noptions){
        return reject(new Error("One of the following: zkConStr or kafkaHost must be defined."));
      }

      if(conStr === zkConStr){
        return reject(new Error("NProducer does not support zookeeper connection."));
      }

      const config = {
        "metadata.broker.list": conStr,
        "dr_cb": true
      }; //TODO transfer rest of config fields

      noptions = noptions || {};
      noptions = Object.assign({}, config, noptions);
      logger.debug(noptions);

      tconf = tconf ? tconf : {
        "request.required.acks": 1
      };
      logger.debug(tconf);

      this.producer = new BlizzKafka.Producer(noptions, tconf);

      this.producer.on("event.log", log => {
        logger.debug(log.message);
      });

      this.producer.on("event.error", error => {
        super.emit("error", error);
      });

      this.producer.on("error", error => {
        super.emit("error", error);
      });

      this.producer.on("delivery-report", (error, report) => {
        logger.debug("DeliveryReport: " + JSON.stringify(report));
      });

      this.producer.on("disconnected", () => {
        if(this._inClosing){
          this._reset();
        }
        logger.warn("Disconnected.");
        //auto-reconnect??? -> handled by producer.poll()
      });

      this.producer.on("ready", () => {

        logger.info(`Native producer ready v. ${BlizzKafka.librdkafkaVersion}, e. ${BlizzKafka.features.join(", ")}.`);

        //poll broker for updates
        this._producerPollIntv = setInterval(() => {
          if(this.producer){
            this.producer.poll();
          }
        }, pollIntervalMs || 100);

        super.emit("ready");
      });

      logger.debug("Connecting..");
      this.producer.connect(null, (error, metadata) => {

        if(error){
          super.emit("error", error);
          return reject(error);
        }

        logger.debug(metadata);
        resolve();
      });
    });
  }

  _getPartitionForKey(key, partitionCount = 0){

    /*
    if(typeof key !== "string"){
      return Promise.reject("Key must be a valid string");
    } */

    if(partitionCount === 0){
      partitionCount = this.defaultPartitionCount;
    }

    return murmur(key) % partitionCount;
  }

  send(topicName, message, _partition = null, _key = null) {

    if (!this.producer) {
      return Promise.reject("producer is not yet setup.");
    }

    if(this.paused){
      return Promise.reject("producer is paused.");
    }

    if(!message || !(typeof message === "string" || Buffer.isBuffer(message))){
      return Promise.reject("message must be a string or an instance of Buffer.");
    }

    let partition = -1;
    if (this.defaultPartitionCount < 2) {
      partition = 0;
    } else {
      partition = NProducer._getRandomIntInclusive(0, this.defaultPartitionCount);
    }

    partition = _partition ? _partition : partition;
    const key = _key ? _key : uuid.v4();
    message = Buffer.isBuffer(message) ? message : new Buffer(message);

    this.config.logger.debug(JSON.stringify({topicName, partition, key}));
    const producedAt = Date.now();

    try {
      this.producer.produce(topicName, partition, message, key, producedAt);
      this._lastProcessed = producedAt;
      this._totalSentMessages++;
      return Promise.resolve({
        key,
        partition
      });
    } catch(error){
      return Promise.reject(error);
    }
  }

  buffer(topic, identifier, payload, partition = null, version = null) {

    if (typeof identifier === "undefined") {
      identifier = uuid.v4();
    }

    if (typeof identifier !== "string") {
      identifier = identifier + "";
    }

    if(typeof payload !== "object"){
      return Promise.reject("expecting payload to be of type object.");
    }

    if(typeof payload.id === "undefined"){
      payload.id = identifier;
    }

    if(version && typeof payload.version === "undefined"){
      payload.version = version;
    }

    partition = partition ? partition : this._getPartitionForKey(identifier);

    return this.send(topic, JSON.stringify(payload), partition, identifier);
  }

  _sendBufferFormat(topic, identifier, _payload, version = 1, _, partitionKey = null, partition = null, messageType = "") {

    if (typeof identifier === "undefined") {
      identifier = uuid.v4();
    }

    if (typeof identifier !== "string") {
      identifier = identifier + "";
    }

    if(typeof _payload !== "object"){
      return Promise.reject("expecting payload to be of type object.");
    }

    if(typeof _payload.id === "undefined"){
      _payload.id = identifier;
    }

    if(version && typeof _payload.version === "undefined"){
      _payload.version = version;
    }

    const payload = {
      payload: _payload,
      key: identifier,
      id: uuid.v4(),
      time: (new Date()).toISOString(),
      type: topic + messageType
    };

    partition = partition ? partition : this._getPartitionForKey(partitionKey ? partitionKey : identifier);
    return this.send(topic, JSON.stringify(payload), partition, identifier);
  }

  /**
   * alias
   */
  bufferFormat(topic, identifier, payload, version = 1, compressionType = 0, partitionKey = null) {
    this.bufferFormatPublish(topic, identifier, payload, version, compressionType, partitionKey);
  }

  bufferFormatPublish(topic, identifier, _payload, version = 1, _, partitionKey = null, partition = null){
    return this._sendBufferFormat(topic, identifier, _payload, version, _, partitionKey, partition, MESSAGE_TYPES.PUBLISH);
  }

  bufferFormatUpdate(topic, identifier, _payload, version = 1, _, partitionKey = null, partition = null){
    return this._sendBufferFormat(topic, identifier, _payload, version, _, partitionKey, partition, MESSAGE_TYPES.UPDATE);
  }

  bufferFormatUnpublish(topic, identifier, _payload, version = 1, _, partitionKey = null, partition = null){
    return this._sendBufferFormat(topic, identifier, _payload, version, _, partitionKey, partition, MESSAGE_TYPES.UNPUBLISH);
  }

  pause() {
    this.paused = true;
  }

  resume() {
    this.paused = false;
  }

  getStats() {
    return {
      totalPublished: this._totalSentMessages,
      last: this._lastProcessed,
      isPaused: this.paused
    };
  }

  refreshMetadata() {
    throw new Error("refreshMetadata is not available for NProducer.");
  }

  _reset(){
    this._lastProcessed = null;
    this._totalSentMessages = 0;
    this.paused = false;
    this._inClosing = false;
  }

  close() {

    if (this.producer) {
      this._inClosing = true;
      clearInterval(this._producerPollIntv);
      this.producer.disconnect();
      //this.producer = null;
    }
  }

  static _getRandomIntInclusive(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
}

module.exports = NProducer;
<|endoftext|>


<commit_before>const accessSync = require('fs-access').sync
const chalk = require('chalk')
const checkpoint = require('../checkpoint')
const conventionalChangelog = require('conventional-changelog')
const fs = require('fs')
const presetLoader = require('../preset-loader')
const runLifecycleScript = require('../run-lifecycle-script')
const writeFile = require('../write-file')
const START_OF_LAST_RELEASE_PATTERN = /(^#+ \[?[0-9]+\.[0-9]+\.[0-9]+| {
      return outputChangelog(args, newVersion)
    })
    .then(() => {
      return runLifecycleScript(args, 'postchangelog')
    })
}

Changelog.START_OF_LAST_RELEASE_PATTERN = START_OF_LAST_RELEASE_PATTERN

module.exports = Changelog

function outputChangelog (args, newVersion) {
  return new Promise((resolve, reject) => {
    createIfMissing(args)
    const header = args.changelogHeader || '# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n'

    let oldContent = args.dryRun ? '' : fs.readFileSync(args.infile, 'utf-8')
    let oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN)
    // find the position of the last release and remove header:
    if (oldContentStart !== -1) {
      oldContent = oldContent.substring(oldContentStart)
    }
    let content = ''
    let context
    if (args.dryRun) context = { version: newVersion }
    let changelogStream = conventionalChangelog({
      debug: args.verbose && console.info.bind(console, 'conventional-changelog'),
      preset: presetLoader(args),
      tagPrefix: args.tagPrefix
    }, context, { merges: null, path: args.path })
      .on('error', function (err) {
        return reject(err)
      })

    changelogStream.on('data', function (buffer) {
      content += buffer.toString()
    })

    changelogStream.on('end', function () {
      checkpoint(args, 'outputting changes to %s', [args.infile])
      if (args.dryRun) console.info(`\n---\n${chalk.gray(content.trim())}\n---\n`)
      else writeFile(args, args.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'))
      return resolve()
    })
  })
}

function createIfMissing (args) {
  try {
    accessSync(args.infile, fs.F_OK)
  } catch (err) {
    if (err.code === 'ENOENT') {
      checkpoint(args, 'created %s', [args.infile])
      args.outputUnreleased = true
      writeFile(args, args.infile, '\n')
    }
  }
}
<commit_msg>fix: always pass version to changelog context (#327)<commit_after>const accessSync = require('fs-access').sync
const chalk = require('chalk')
const checkpoint = require('../checkpoint')
const conventionalChangelog = require('conventional-changelog')
const fs = require('fs')
const presetLoader = require('../preset-loader')
const runLifecycleScript = require('../run-lifecycle-script')
const writeFile = require('../write-file')
const START_OF_LAST_RELEASE_PATTERN = /(^#+ \[?[0-9]+\.[0-9]+\.[0-9]+| {
      return outputChangelog(args, newVersion)
    })
    .then(() => {
      return runLifecycleScript(args, 'postchangelog')
    })
}

Changelog.START_OF_LAST_RELEASE_PATTERN = START_OF_LAST_RELEASE_PATTERN

module.exports = Changelog

function outputChangelog (args, newVersion) {
  return new Promise((resolve, reject) => {
    createIfMissing(args)
    const header = args.changelogHeader || '# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n'

    let oldContent = args.dryRun ? '' : fs.readFileSync(args.infile, 'utf-8')
    let oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN)
    // find the position of the last release and remove header:
    if (oldContentStart !== -1) {
      oldContent = oldContent.substring(oldContentStart)
    }
    let content = ''
    const context = { version: newVersion }
    let changelogStream = conventionalChangelog({
      debug: args.verbose && console.info.bind(console, 'conventional-changelog'),
      preset: presetLoader(args),
      tagPrefix: args.tagPrefix
    }, context, { merges: null, path: args.path })
      .on('error', function (err) {
        return reject(err)
      })

    changelogStream.on('data', function (buffer) {
      content += buffer.toString()
    })

    changelogStream.on('end', function () {
      checkpoint(args, 'outputting changes to %s', [args.infile])
      if (args.dryRun) console.info(`\n---\n${chalk.gray(content.trim())}\n---\n`)
      else writeFile(args, args.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'))
      return resolve()
    })
  })
}

function createIfMissing (args) {
  try {
    accessSync(args.infile, fs.F_OK)
  } catch (err) {
    if (err.code === 'ENOENT') {
      checkpoint(args, 'created %s', [args.infile])
      args.outputUnreleased = true
      writeFile(args, args.infile, '\n')
    }
  }
}
<|endoftext|>