<commit_before>// TODO
game.PlayerEntity = me.Entity.extend({// game and me .Entity is a class
	init: function (x, y, settings) { // this is our constructor function
		this.setSuper();
		this.setPlayerTimers();
		this.setAttributes();
		//from video 24 all the game.date info are all stored in the game.entities.
	   	this.type= "PlayerEntity";
	   	this.setFlags();

	   	me.game.viewport.follow(this.pos, me.game.viewport. AXIS.BOTH);

	   	this.addanimation();

	    this.renderable.setCurrentAnimation("idle");
	},
	setSuper: function(){
		this._super(me.Entity, 'init', [x, y,{
			image: "player",
			width: 64,
			height: 64,
			spritewidth: "64",
			spriteheight: "64",
			getShape: function (){
				return(new me.Rect(0, 0, 64, 64)).toPolygon();

				}
		}]);
	},
	setPlayerTimers: function(){
		this.now = new Date().getTime();// it keep track of time
	   	this.lastHit = this.now;
	   	this.lastAttack = new Date().getTime();  //Haven't used this
	},
	setAttributes: function(){
		this.health = game.data.PlayerHealth;// player's health are stored in the game; date
	    this.body.setVelocity(game.data.PlayerMoveSpeed, 20); // this part sets the speed of the player
	    this.attack = game.data.PlayerAttack;
	},
	setFlags: function(){
		this.facing = "right";//keeps track of which direction your character is going
	   	this.dead = false;
	   	this.attacking = false;
	},
	addanimation: function(){
		//this renderable addanimation idle sets the animation went nothing is pressing
	    this.renderable.addAnimation("idle", [78]);
	    this.renderable.addAnimation("walk", [117, 118, 119, 120, 121, 122, 123, 124, 125], 80);
	    // this renderable addanimation controls the animation walk
	   	this.renderable.addAnimation("attack", [65, 66, 67, 69, 70, 71, 72],80);
	},
	update: function (delta){
		this.now = new Date().getTime();
		this.dead = checkIfDead();
		this.checkKeyPressesAndMove();
		this.setAnimation();

		me.collision.check(this, true, this.collideHandler.bind(this), true);
		this.body.update(delta);

		this._super(me.Entity, "update", [delta]);// the code delta runs the animation from your renderable addanimation.
		return true;
	},

	checkIfDead: function(){
		if (this.health <= 0) {
			return true;
		}
		return false;
	},

	checkKeyPressesAndMove: function(){

		if(me.input.isKeyPressed("right")){
			this.moveRight();
		}else if(me.input.isKeyPressed("left")){
			this.moveLeft();
		}
		else{
			this.body.vel.x = 0;
		}

		if(me.input.isKeyPressed("jump")){

			if(!this.body.jumping && !this.body.falling) {

				this.body.vel.y = -this.body.maxVel.y * me.timer.tick;
				this.body.jumping = true;

			}

		}

		this.attacking = me.input.isKeyPressed("attack");
	},

	moveRight: function(){
		//sets the position of my x by adding the velocity defined above in.
			//seVelocity() and multiplying itby me.timer.tick.
			//me.timer.tick makes the movement look smoot
			this.body.vel.x += this.body.accel.x * me.timer.tick;
			this.facing = "right";

			this.flipX(true);
	},
	moveLeft: function(){
		this.facing = "left";
			this.body.vel.x -=this.body.accel.x * me.timer.tick;
			this.flipX(false);

	},
	setAnimation: function(){
		if(this.attacking){

			if(!this.renderable.isCurrentAnimation("attack")){

				// sets the current animation to attack and once that is over
				//goes back to the idle animation
				this.renderable.setCurrentAnimation("attack", "idle");
				//makes it so that the next timme we start this sequence we begin
				//from the first animation, not wherever we left off when we
				//switched to tanother animation
				this.renderable.setAnimationFrame();

			}
		}
			else if(this.body.vel.x !== 0 && !this.renderable.isCurrentAnimation("attack")){

		if(!this.renderable.isCurrentAnimation("walk")){
			this.renderable.setCurrentAnimation("walk");
		}
			}else if(!this.renderable.isCurrentAnimation("attack")){
			this.renderable.setCurrentAnimation("idle");
	}

	},
	loseHealth: function(damage){
			this.health = this.health - damage;

	},

	collideHandler: function(response){
		if (response.b.type==='EnemyBaseEntity') {
			this.collideWithEnemyBase(response);
		}
		else if (response.b.type==='EnemyCreep') {
			this.collideWithEnemyCreep(response);
		}

	},

		collideWithEnemyBase: function(response){
			var ydif = this.pos.y - response.b.pos.y;
			var xdif = this.pos.x - response.b.pos.x;

			console.log("xdif" + xdif + "ydif" + ydif);

			if (ydif<-40 && xdif< 70 && xdif>-35) {// player collide with the top of the base
				this.body.falling = false;
				this.body.vel.y = -1;
			}else if(xdif>-35 && this.facing==='right' && (xdif<0)){ // when player collide with enemybase it will stop the player from the right side
				this.body.vel.x = 0;

			}else if (xdif<70 && this.facing==='left' && xdif>0) {// the left side
				this.body.vel.x = 0;

			}
	if(this.renderable.isCurrentAnimation("attack") && this.now-this.lastHit >= game.data.PlayerAttackTimer){

				this.lastHit = this.now;
				response.b.loseHealth(game.data.PlayerAttack);
			}
		},
		collideWithEnemyCreep: function(response){
			var xdif = this.pos.x - response.b.pos.x;
			var ydif = this.pos.y - response.b.pos.y;

			this.stopMovement(xdif);
			if(this.checkAttack(xdif, ydif)){
				this.hitCreep(response);
			};

		},

		stopMovement: function(xdif){
			if(xdif>0){
				if (this.facing==="left") {
					this.body.vel.x = 0;
				}
			}else{
				if (this.facing==="right") {
					this.body.vel.x = 0;
				}
			}
		},
		checkAttack: function(xdif, ydif){
			if(this.renderable.isCurrentAnimation("attack") && this.now-this.lastHit >= game.data.PlayerAttackTimer
				&& (Math.abs(ydif) <=40) &&
				(((xdif>0) && this.facing==="left") || ((xdif<0) && this.facing==="right"))
				){
				this.lastHit = this.now;
					//if the creeps health is less than our attack, execute code in if statement
				return true;
			}
			return false;
		},

		hitCreep: function(response){
			if (response.b.health <= game.data.PlayerAttack) {
					//adds one gold for a creep kill
					game.data.gold += 1;
					console.log("Current gold" + game.data.gold);
				}
					response.b.loseHealth(game.data.PlayerAttack);
		}
});

<commit_msg>video 34 coded and error fixed<commit_after>// TODO
game.PlayerEntity = me.Entity.extend({// game and me .Entity is a class
	init: function (x, y, settings) { // this is our constructor function
		this.setSuper();
		this.setPlayerTimers();
		this.setAttributes();
		//from video 24 all the game.date info are all stored in the game.entities.
	   	this.type= "PlayerEntity";
	   	this.setFlags();

	   	me.game.viewport.follow(this.pos, me.game.viewport. AXIS.BOTH);

	   	this.addanimation();

	    this.renderable.setCurrentAnimation("idle");
	},
	setSuper: function(x, y){
		this._super(me.Entity, 'init', [x, y,{
			image: "player",
			width: 64,
			height: 64,
			spritewidth: "64",
			spriteheight: "64",
			getShape: function (){
				return(new me.Rect(0, 0, 64, 64)).toPolygon();

				}
		}]);
	},
	setPlayerTimers: function(){
		this.now = new Date().getTime();// it keep track of time
	   	this.lastHit = this.now;
	   	this.lastAttack = new Date().getTime();  //Haven't used this
	},
	setAttributes: function(){
		this.health = game.data.PlayerHealth;// player's health are stored in the game; date
	    this.body.setVelocity(game.data.PlayerMoveSpeed, 20); // this part sets the speed of the player
	    this.attack = game.data.PlayerAttack;
	},
	setFlags: function(){
		this.facing = "right";//keeps track of which direction your character is going
	   	this.dead = false;
	   	this.attacking = false;
	},
	addanimation: function(){
		//this renderable addanimation idle sets the animation went nothing is pressing
	    this.renderable.addAnimation("idle", [78]);
	    this.renderable.addAnimation("walk", [117, 118, 119, 120, 121, 122, 123, 124, 125], 80);
	    // this renderable addanimation controls the animation walk
	   	this.renderable.addAnimation("attack", [65, 66, 67, 69, 70, 71, 72],80);
	},
	update: function (delta){
		this.now = new Date().getTime();
		this.dead = this.checkIfDead();
		this.checkKeyPressesAndMove();
		this.setAnimation();

		me.collision.check(this, true, this.collideHandler.bind(this), true);
		this.body.update(delta);

		this._super(me.Entity, "update", [delta]);// the code delta runs the animation from your renderable addanimation.
		return true;
	},

	checkIfDead: function(){
		if (this.health <= 0) {
			return true;
		}
		return false;
	},

	checkKeyPressesAndMove: function(){

		if(me.input.isKeyPressed("right")){
			this.moveRight();
		}else if(me.input.isKeyPressed("left")){
			this.moveLeft();
		}
		else{
			this.body.vel.x = 0;
		}

		if(me.input.isKeyPressed("jump")){

			if(!this.body.jumping && !this.body.falling) {

				this.body.vel.y = -this.body.maxVel.y * me.timer.tick;
				this.body.jumping = true;

			}

		}

		this.attacking = me.input.isKeyPressed("attack");
	},

	moveRight: function(){
		//sets the position of my x by adding the velocity defined above in.
			//seVelocity() and multiplying itby me.timer.tick.
			//me.timer.tick makes the movement look smoot
			this.body.vel.x += this.body.accel.x * me.timer.tick;
			this.facing = "right";

			this.flipX(true);
	},
	moveLeft: function(){
		this.facing = "left";
			this.body.vel.x -=this.body.accel.x * me.timer.tick;
			this.flipX(false);

	},
	setAnimation: function(){
		if(this.attacking){

			if(!this.renderable.isCurrentAnimation("attack")){

				// sets the current animation to attack and once that is over
				//goes back to the idle animation
				this.renderable.setCurrentAnimation("attack", "idle");
				//makes it so that the next timme we start this sequence we begin
				//from the first animation, not wherever we left off when we
				//switched to tanother animation
				this.renderable.setAnimationFrame();

			}
		}
			else if(this.body.vel.x !== 0 && !this.renderable.isCurrentAnimation("attack")){

		if(!this.renderable.isCurrentAnimation("walk")){
			this.renderable.setCurrentAnimation("walk");
		}
			}else if(!this.renderable.isCurrentAnimation("attack")){
			this.renderable.setCurrentAnimation("idle");
	}

	},
	loseHealth: function(damage){
			this.health = this.health - damage;

	},

	collideHandler: function(response){
		if (response.b.type==='EnemyBaseEntity') {
			this.collideWithEnemyBase(response);
		}
		else if (response.b.type==='EnemyCreep') {
			this.collideWithEnemyCreep(response);
		}

	},

		collideWithEnemyBase: function(response){
			var ydif = this.pos.y - response.b.pos.y;
			var xdif = this.pos.x - response.b.pos.x;

			console.log("xdif" + xdif + "ydif" + ydif);

			if (ydif<-40 && xdif< 70 && xdif>-35) {// player collide with the top of the base
				this.body.falling = false;
				this.body.vel.y = -1;
			}else if(xdif>-35 && this.facing==='right' && (xdif<0)){ // when player collide with enemybase it will stop the player from the right side
				this.body.vel.x = 0;

			}else if (xdif<70 && this.facing==='left' && xdif>0) {// the left side
				this.body.vel.x = 0;

			}
	if(this.renderable.isCurrentAnimation("attack") && this.now-this.lastHit >= game.data.PlayerAttackTimer){

				this.lastHit = this.now;
				response.b.loseHealth(game.data.PlayerAttack);
			}
		},
		collideWithEnemyCreep: function(response){
			var xdif = this.pos.x - response.b.pos.x;
			var ydif = this.pos.y - response.b.pos.y;

			this.stopMovement(xdif);
			if(this.checkAttack(xdif, ydif)){
				this.hitCreep(response);
			};

		},

		stopMovement: function(xdif){
			if(xdif>0){
				if (this.facing==="left") {
					this.body.vel.x = 0;
				}
			}else{
				if (this.facing==="right") {
					this.body.vel.x = 0;
				}
			}
		},
		checkAttack: function(xdif, ydif){
			if(this.renderable.isCurrentAnimation("attack") && this.now-this.lastHit >= game.data.PlayerAttackTimer
				&& (Math.abs(ydif) <=40) &&
				(((xdif>0) && this.facing==="left") || ((xdif<0) && this.facing==="right"))
				){
				this.lastHit = this.now;
					//if the creeps health is less than our attack, execute code in if statement
				return true;
			}
			return false;
		},

		hitCreep: function(response){
			if (response.b.health <= game.data.PlayerAttack) {
					//adds one gold for a creep kill
					game.data.gold += 1;
					console.log("Current gold" + game.data.gold);
				}
					response.b.loseHealth(game.data.PlayerAttack);
		}
});

<|endoftext|>


<commit_before>// ==UserScript==
// @name              PixivAutoTag.user.js
// @description       Pixivのブックマークタグ付けを半自動化してくれます https://github.com/syusui-s/PixivAutoTag.user.js
// @match             https://www.pixiv.net/bookmark_add.php?*
// @match             https://www.pixiv.net/member_illust.php?*illust_id=*
// @match             https://www.pixiv.net/bookmark.php*
// @run-at            document-end
// @version           0.3.2
// ==/UserScript==
'use strict';

// デフォルトルール
var defaultRuleStr = 'private R-18';

// アップデート確認用URI
var updateCheckURI = 'https://api.github.com/repos/syusui-s/PixivAutoTag.user.js/git/refs/heads/master';

//
var textareaUpdateFlag = false;

// 設定テキストを解析し、連想配列で返す
function parseRules(ruleStr) {
	var patternRule     = [];
	var patternAllRule  = [];
	var additionRule    = [];
	var additionAllRule = [];
	var privateRule     = [];
	var errors          = [];

	// 正規表現を表す文字列のリストから、正規表現のリストを作成
	function createRegExpFromStrAry(regexpStrAry, lineNumber) {
		var regexps = [];
		regexpStrAry.forEach((regexpStr) => {
			try { regexps.push(new RegExp(regexpStr)); }
			catch (e) { errors.push({ lineNumber: lineNumber, message: ('正規表現のエラーです（' + e.name + ':' + e.message + '）。内容: ' + regexpStr) }); }
		});
		return regexps;
	};

	// strに完全一致する正規表現を生成
	function createRegExpPerfectMatch(str) {
		return new RegExp('^' + str.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&') + '$');
	};

	// タグ、正規表現リスト、追加先を受け取り、追加する
	function addRule(tag, regexps, rules) {
		var rule = { tag: tag, regexps: regexps };
		rules.push(rule);
	};

	ruleStr.split('\n').forEach((line, num) => {
		var parsed = line.split(/\s+/);
		var matchData;

		var matchPattern = /^(pattern|match|addition_pattern)(|_all)$/i;

		if ( parsed.length >= 3 && (matchData = parsed[0].match(matchPattern)) ) {
			var tag = parsed[1];
			var type = matchData[1];
			var isSome = matchData[2].length === 0;
			var regexps;

			if ( type === 'match' ) {  // 一致
 				var rules = isSome ? patternRule : patternAllRule;
				var match_tags = parsed.slice(2);
				regexps = match_tags.map(createRegExpPerfectMatch);

				addRule(tag, regexps, rules);
			} else if ( type === 'pattern' ) { // 正規表現
 				var rules = isSome ? patternRule : patternAllRule;
				var str_regexps = parsed.slice(2);
				regexps = createRegExpFromStrAry(str_regexps, num + 1);

				addRule(tag, regexps, rules);
			} else if ( type === 'addition_pattern' ) { // 追加タグ
 				var rules = isSome ? additionRule : additionAllRule
				var str_regexps = parsed.slice(2);
				regexps = createRegExpFromStrAry(str_regexps, num + 1);

				addRule(tag, regexps, rules);
			} else {
				errors.push({
					lineNumber: (num+1),
					message: ('予期しないエラーが発生しました。作者にお知らせください。内容: ' + line)
				});
				return;
			}
		} else if ( parsed.length >= 2 && parsed[0].match(/^private$/i) ) { // 非公開タグ
			var rules = parsed.slice(1);
			privateRule.push.apply(privateRule, rules);
		} else if ( line.match(/^\s*$|^\s*#/) ) { // 空行 or コメント行
			// nothing to do
		} else {
			errors.push({
				lineNumber: (num+1),
				message: ('不正なコマンドを使用しているか、引数が少なすぎます。内容: ' + line)
			});
			return false;
		}
	});
	return {
		privateRule:     privateRule,
		patternRule:     patternRule,
		patternAllRule:  patternAllRule,
		additionRule:    additionRule,
		additionAllRule: additionAllRule,
		errors:          errors
	};
};

// matchDataの配列を渡すと文中の~数字をそれで置き換える
function replaceWithMatch(input, matchData) {
	return input.split(/(~\d)/).map((str) => {
		var match = str.match(/^~(\d)/);
		if ( match ) { return matchData[parseInt(match[1])] || ''; }
		return str;
	}).join('');
};

function removeSettingsView() {
	if (textareaUpdateFlag) {
		if (window.confirm('設定に変更が存在します。保存しますか？')) {
			document.querySelector('#autotag-settings-view input[type="submit"]').click();
			textareaUpdateFlag = false;
		} else if (! window.confirm('変更を破棄してもよろしいですか？')) {
			return;
		}
	}

	var textarea = document.querySelector('#autotag-settings-view');
	textarea.remove();
	return;
};

// 設定画面の生成・消去
function toggleSettingsView() {
	// すでに存在していたなら消去
	var settingsView = document.querySelector('#autotag-settings-view');
	if (settingsView) {
		removeSettingsView();
		return;
	}

	textareaUpdateFlag = false;

	// 保存済みデータの取得
	var ruleStr = window.localStorage.pixivAutoTag_taggingRule || defaultRuleStr;

	// フォームの作成
	settingsView = document.createElement('form');
	settingsView.id = 'autotag-settings-view';
	settingsView.style.backgroundColor = '#FFFFFF';
	settingsView.style.marginTop    = '5px';
	settingsView.style.padding      = '10px 9px';
	settingsView.style.borderRadius = '5px';

	var paragraph = document.createElement('p');
	var label = document.createElement('lavel');
	label.textContent = 'タグ付けルール';
	var br = document.createElement('br');
	var textarea = document.createElement('textarea');
	textarea.id = 'autotag-settings-tagging-rule';
	textarea.cols = '80';
	textarea.rows = '10';
	textarea.style.height = 'auto';
	textarea.defaultValue = ruleStr;
	textarea.addEventListener('input', (ev) => { textareaUpdateFlag = true; });

	paragraph.appendChild(label);
	paragraph.appendChild(br);
	paragraph.appendChild(textarea);
	settingsView.appendChild(paragraph);

	var buttonsParagraph = document.createElement('p');

	var saveButton = document.createElement('input');
	saveButton.type = 'submit';
	saveButton.className = '_button';
	saveButton.value = '保存';

	settingsView.addEventListener('submit', (ev) => {
		var ruleStr = document.querySelector('#autotag-settings-tagging-rule').value;
		var rule = parseRules(ruleStr);
		if (rule.errors.length === 0) {
			window.localStorage.pixivAutoTag_taggingRule = ruleStr;
			alert('保存しました');
			textareaUpdateFlag = false;
		} else {
			alert(rule.errors.map((err) => { return '[Error] line: ' + err.lineNumber + ', message: ' + err.message; }).join('\n'));
		}
		ev.preventDefault();
	});
	buttonsParagraph.appendChild(saveButton);

	var cancelButton = document.createElement('input');
	cancelButton.className = '_button';
	cancelButton.type = 'reset';
	cancelButton.value = 'キャンセル';
	buttonsParagraph.appendChild(cancelButton);

	var downloadButton = document.createElement('input');
	downloadButton.className = '_button';
	downloadButton.type = 'button';
	downloadButton.value = '設定のダウンロード';
	downloadButton.addEventListener('click', (ev) => {
		var a = document.createElement('a');
		var date = new Date();
		a.href = textarea.value;
		a.download = 'pixiv_auto_tag-' + date.getTime() + '.txt';
		a.click();
	});
	buttonsParagraph.appendChild(downloadButton);

	settingsView.appendChild(buttonsParagraph);

	var settingsButton = document.querySelector('#autotag-settings-button');
	settingsButton.parentElement.insertBefore(settingsView, settingsButton.nextSibling);
};

// タグの自動追加関数
function autoTag() {
	var ruleText = window.localStorage.pixivAutoTag_taggingRule || defaultRuleStr;
	var rule = parseRules(ruleText);

	// 作品タグとタグクラウドの取得
	var tagCloud = Array.from(
			document.querySelectorAll('section.tag-cloud-container > ul.tag-cloud > li')
		).map((item) => item.textContent);

	var tagsExist = Array.from(
			document.querySelectorAll('div.recommend-tag > ul span.tag')
		).reduce((tags, item) => {
			var tagText = item.textContent.replace(/^\*/, '');

			tags.push( tagText );

			if (tagText.includes('/')) {
				tags.push(...tagText.split('/'));
			}

			return tags;
		}, []);

	// 非公開タグが含まれていた場合、自動で非公開に設定
	var privateButton = document.querySelector('ul.privacy input[value="1"]');
	if (tagsExist.some((tag) =>{ return rule.privateRule.indexOf(tag) !== -1; })) {
		privateButton.checked = true;
	}

	// tags: タグ配列, rules: ルール配列, tags: 出力先タグ配列
	function applySomeRule(tags, rules, array) {
		tags.forEach((tag) => { // それぞれのタグに対して
			rules.forEach((rule) => { // それぞれのルールの
				var matchData = [];
				var foundMatchFlag = rule.regexps.some((pattern) => { // いずれかの正規表現が一致するか？
					return (matchData = tag.match(pattern));
				});
				if (foundMatchFlag) { array.push(replaceWithMatch(rule.tag, matchData)); }
			});
		});
	};

	function applyAllRule(tags, rules, array) {
		rules.forEach((rule) => { // それぞれのルール文の
			var matchData = [];
			var foundMatchFlag = rule.regexps.every((pattern) => { // すべてのパターンが
				return tags.some((tag) => { return (matchData = tag.match(pattern)); }); // いずれかの作品タグに一致するか？
			});
			if (foundMatchFlag) { array.push(replaceWithMatch(rule.tag, matchData)); }
		});
	};

	// タグ入力欄
	var input = document.querySelector('#input_tag');

	// ブックマークタグリストの生成
	if (input && input.value.length === 0) {
		// 作品タグに追加
		applySomeRule(tagsExist, rule.additionRule, tagsExist);
		applyAllRule(tagsExist, rule.additionAllRule, tagsExist);

		// 作品タグとタグクラウドの共通タグを抽出
		var tagsFound = tagsExist.filter((existTag) => {
			return tagCloud.some((cloudTag) =>{
				return existTag === cloudTag;
			});
		});

		// 付与タグリストの生成
		var tagsAdded = [];
		applySomeRule(tagsExist, rule.patternRule, tagsAdded);
		applyAllRule(tagsExist, rule.patternAllRule, tagsAdded);

		// タグを消去する
		tagsFound = tagsFound.filter((foundTag) => { return tagsAdded.indexOf('-'+foundTag) === -1; });
		tagsAdded = tagsAdded.filter((tag) => { return !tag.match(/^-/); });

		// 重複の削除
		var uniqueAry = tagsFound.concat(tagsAdded).filter((elem, i, ary) => { return ary.indexOf(elem) === i; }).join(' ');

		// ブックマークタグを設定する
		input.value = uniqueAry;

		// タグのハイライトを表示させる
		var keyupEvent = document.createEvent('HTMLEvents');
		keyupEvent.initEvent('keyup', true, true);
		input.dispatchEvent(keyupEvent);

		// タグ付けが終わったら、背景を緑色にして補完がされたことを示す
		input.parentNode.style.backgroundColor = '#76B6E0';
		// それと入力欄にフォーカスする
		input.focus();
	}
};

function generateButtons() {
	// 設定ボタンの生成
	var settingsButton = document.createElement('button');
	settingsButton.className = '_button';
	settingsButton.style.marginLeft = '1em';
	settingsButton.id = 'autotag-settings-button';
	settingsButton.textContent = 'タグ自動化設定';
	settingsButton.addEventListener('click', toggleSettingsView, false);

	// タグ付けボタンの生成
	var autoTagButton = document.createElement('button');
	autoTagButton.className = '_button';
	autoTagButton.style.marginLeft = '0.25em';
	autoTagButton.id = 'autotag-button';
	autoTagButton.textContent = '上書きタグ付け';
	autoTagButton.addEventListener('click', () => {
		var input = document.querySelector('#input_tag');
		input.value = '';
		autoTag();
	}, false);

	var target = document.querySelector('div.recommend-tag > h1');
	target.parentElement.insertBefore(settingsButton, target.nextSibling);
	target.parentElement.insertBefore(autoTagButton, settingsButton.nextSibling);
};

(() => {
	window.addEventListener('PixivAutoTag.autoTag', () => autoTag() );
	window.addEventListener('PixivAutoTag.generateButtons', () => generateButtons() );

	window.addEventListener('beforeunload', (e) => {
		if (textareaUpdateFlag) {
			var confirmationMessage = "PixivAutoTag.user.js の設定が変更されています。破棄してもよろしいですか？";
			e.returnValue = confirmationMessage;
			return confirmationMessage;
		}
	});

	// 自動タグ付けの実行
	if (/member_illust.php/.test(location.href)) {
		var ul = document.querySelector('section.list-container.tag-container.work-tags-container > div > ul');
		function checkTagGenerated(fn) { (ul.childNodes.length > 0) ? window.setTimeout(autoTag, 750) : window.setTimeout(fn, 750, fn); };
		window.setTimeout(checkTagGenerated, 1250, checkTagGenerated);
		generateButtons();
	} else if (/bookmark_add/.test(location.href)) {
		window.setTimeout(autoTag, 750);
		generateButtons();
	}

})();
<commit_msg>enhancement: 小説のブックマークに対応した<commit_after>// ==UserScript==
// @name              PixivAutoTag.user.js
// @description       Pixivのブックマークタグ付けを半自動化してくれます https://github.com/syusui-s/PixivAutoTag.user.js
// @match             https://www.pixiv.net/bookmark_add.php?*
// @match             https://www.pixiv.net/member_illust.php?*illust_id=*
// @match             https://www.pixiv.net/novel/show.php?id=*
// @match             https://www.pixiv.net/novel/bookmark_add.php?id=*
// @match             https://www.pixiv.net/bookmark.php*
// @run-at            document-end
// @version           0.3.3
// ==/UserScript==
'use strict';

// デフォルトルール
var defaultRuleStr = 'private R-18';

// アップデート確認用URI
var updateCheckURI = 'https://api.github.com/repos/syusui-s/PixivAutoTag.user.js/git/refs/heads/master';

//
var textareaUpdateFlag = false;

// 設定テキストを解析し、連想配列で返す
function parseRules(ruleStr) {
	var patternRule     = [];
	var patternAllRule  = [];
	var additionRule    = [];
	var additionAllRule = [];
	var privateRule     = [];
	var errors          = [];

	// 正規表現を表す文字列のリストから、正規表現のリストを作成
	function createRegExpFromStrAry(regexpStrAry, lineNumber) {
		var regexps = [];
		regexpStrAry.forEach((regexpStr) => {
			try { regexps.push(new RegExp(regexpStr)); }
			catch (e) { errors.push({ lineNumber: lineNumber, message: ('正規表現のエラーです（' + e.name + ':' + e.message + '）。内容: ' + regexpStr) }); }
		});
		return regexps;
	};

	// strに完全一致する正規表現を生成
	function createRegExpPerfectMatch(str) {
		return new RegExp('^' + str.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&') + '$');
	};

	// タグ、正規表現リスト、追加先を受け取り、追加する
	function addRule(tag, regexps, rules) {
		var rule = { tag: tag, regexps: regexps };
		rules.push(rule);
	};

	ruleStr.split('\n').forEach((line, num) => {
		var parsed = line.split(/\s+/);
		var matchData;

		var matchPattern = /^(pattern|match|addition_pattern)(|_all)$/i;

		if ( parsed.length >= 3 && (matchData = parsed[0].match(matchPattern)) ) {
			var tag = parsed[1];
			var type = matchData[1];
			var isSome = matchData[2].length === 0;
			var regexps;

			if ( type === 'match' ) {  // 一致
 				var rules = isSome ? patternRule : patternAllRule;
				var match_tags = parsed.slice(2);
				regexps = match_tags.map(createRegExpPerfectMatch);

				addRule(tag, regexps, rules);
			} else if ( type === 'pattern' ) { // 正規表現
 				var rules = isSome ? patternRule : patternAllRule;
				var str_regexps = parsed.slice(2);
				regexps = createRegExpFromStrAry(str_regexps, num + 1);

				addRule(tag, regexps, rules);
			} else if ( type === 'addition_pattern' ) { // 追加タグ
 				var rules = isSome ? additionRule : additionAllRule
				var str_regexps = parsed.slice(2);
				regexps = createRegExpFromStrAry(str_regexps, num + 1);

				addRule(tag, regexps, rules);
			} else {
				errors.push({
					lineNumber: (num+1),
					message: ('予期しないエラーが発生しました。作者にお知らせください。内容: ' + line)
				});
				return;
			}
		} else if ( parsed.length >= 2 && parsed[0].match(/^private$/i) ) { // 非公開タグ
			var rules = parsed.slice(1);
			privateRule.push.apply(privateRule, rules);
		} else if ( line.match(/^\s*$|^\s*#/) ) { // 空行 or コメント行
			// nothing to do
		} else {
			errors.push({
				lineNumber: (num+1),
				message: ('不正なコマンドを使用しているか、引数が少なすぎます。内容: ' + line)
			});
			return false;
		}
	});
	return {
		privateRule:     privateRule,
		patternRule:     patternRule,
		patternAllRule:  patternAllRule,
		additionRule:    additionRule,
		additionAllRule: additionAllRule,
		errors:          errors
	};
};

// matchDataの配列を渡すと文中の~数字をそれで置き換える
function replaceWithMatch(input, matchData) {
	return input.split(/(~\d)/).map((str) => {
		var match = str.match(/^~(\d)/);
		if ( match ) { return matchData[parseInt(match[1])] || ''; }
		return str;
	}).join('');
};

function removeSettingsView() {
	if (textareaUpdateFlag) {
		if (window.confirm('設定に変更が存在します。保存しますか？')) {
			document.querySelector('#autotag-settings-view input[type="submit"]').click();
			textareaUpdateFlag = false;
		} else if (! window.confirm('変更を破棄してもよろしいですか？')) {
			return;
		}
	}

	var textarea = document.querySelector('#autotag-settings-view');
	textarea.remove();
	return;
};

// 設定画面の生成・消去
function toggleSettingsView() {
	// すでに存在していたなら消去
	var settingsView = document.querySelector('#autotag-settings-view');
	if (settingsView) {
		removeSettingsView();
		return;
	}

	textareaUpdateFlag = false;

	// 保存済みデータの取得
	var ruleStr = window.localStorage.pixivAutoTag_taggingRule || defaultRuleStr;

	// フォームの作成
	settingsView = document.createElement('form');
	settingsView.id = 'autotag-settings-view';
	settingsView.style.backgroundColor = '#FFFFFF';
	settingsView.style.marginTop    = '5px';
	settingsView.style.padding      = '10px 9px';
	settingsView.style.borderRadius = '5px';

	var paragraph = document.createElement('p');
	var label = document.createElement('lavel');
	label.textContent = 'タグ付けルール';
	var br = document.createElement('br');
	var textarea = document.createElement('textarea');
	textarea.id = 'autotag-settings-tagging-rule';
	textarea.cols = '80';
	textarea.rows = '10';
	textarea.style.height = 'auto';
	textarea.defaultValue = ruleStr;
	textarea.addEventListener('input', (ev) => { textareaUpdateFlag = true; });

	paragraph.appendChild(label);
	paragraph.appendChild(br);
	paragraph.appendChild(textarea);
	settingsView.appendChild(paragraph);

	var buttonsParagraph = document.createElement('p');

	var saveButton = document.createElement('input');
	saveButton.type = 'submit';
	saveButton.className = '_button';
	saveButton.value = '保存';

	settingsView.addEventListener('submit', (ev) => {
		var ruleStr = document.querySelector('#autotag-settings-tagging-rule').value;
		var rule = parseRules(ruleStr);
		if (rule.errors.length === 0) {
			window.localStorage.pixivAutoTag_taggingRule = ruleStr;
			alert('保存しました');
			textareaUpdateFlag = false;
		} else {
			alert(rule.errors.map((err) => { return '[Error] line: ' + err.lineNumber + ', message: ' + err.message; }).join('\n'));
		}
		ev.preventDefault();
	});
	buttonsParagraph.appendChild(saveButton);

	var cancelButton = document.createElement('input');
	cancelButton.className = '_button';
	cancelButton.type = 'reset';
	cancelButton.value = 'キャンセル';
	buttonsParagraph.appendChild(cancelButton);

	var downloadButton = document.createElement('input');
	downloadButton.className = '_button';
	downloadButton.type = 'button';
	downloadButton.value = '設定のダウンロード';
	downloadButton.addEventListener('click', (ev) => {
		var a = document.createElement('a');
		var date = new Date();
		a.href = textarea.value;
		a.download = 'pixiv_auto_tag-' + date.getTime() + '.txt';
		a.click();
	});
	buttonsParagraph.appendChild(downloadButton);

	settingsView.appendChild(buttonsParagraph);

	var settingsButton = document.querySelector('#autotag-settings-button');
	settingsButton.parentElement.insertBefore(settingsView, settingsButton.nextSibling);
};

// タグの自動追加関数
function autoTag() {
	var ruleText = window.localStorage.pixivAutoTag_taggingRule || defaultRuleStr;
	var rule = parseRules(ruleText);

	// 作品タグとタグクラウドの取得
	var tagCloud = Array.from(
			document.querySelectorAll('section.tag-cloud-container > ul.tag-cloud > li')
		).map((item) => item.textContent);

	var tagsExist = Array.from(
			document.querySelectorAll('div.recommend-tag > ul span.tag')
		).reduce((tags, item) => {
			var tagText = item.textContent.replace(/^\*/, '');

			tags.push( tagText );

			if (tagText.includes('/')) {
				tags.push(...tagText.split('/'));
			}

			return tags;
		}, []);

	// 非公開タグが含まれていた場合、自動で非公開に設定
	var privateButton = document.querySelector('ul.privacy input[value="1"]');
	if (tagsExist.some((tag) =>{ return rule.privateRule.indexOf(tag) !== -1; })) {
		privateButton.checked = true;
	}

	// tags: タグ配列, rules: ルール配列, tags: 出力先タグ配列
	function applySomeRule(tags, rules, array) {
		tags.forEach((tag) => { // それぞれのタグに対して
			rules.forEach((rule) => { // それぞれのルールの
				var matchData = [];
				var foundMatchFlag = rule.regexps.some((pattern) => { // いずれかの正規表現が一致するか？
					return (matchData = tag.match(pattern));
				});
				if (foundMatchFlag) { array.push(replaceWithMatch(rule.tag, matchData)); }
			});
		});
	};

	function applyAllRule(tags, rules, array) {
		rules.forEach((rule) => { // それぞれのルール文の
			var matchData = [];
			var foundMatchFlag = rule.regexps.every((pattern) => { // すべてのパターンが
				return tags.some((tag) => { return (matchData = tag.match(pattern)); }); // いずれかの作品タグに一致するか？
			});
			if (foundMatchFlag) { array.push(replaceWithMatch(rule.tag, matchData)); }
		});
	};

	// タグ入力欄
	var input = document.querySelector('#input_tag');

	// ブックマークタグリストの生成
	if (input && input.value.length === 0) {
		// 作品タグに追加
		applySomeRule(tagsExist, rule.additionRule, tagsExist);
		applyAllRule(tagsExist, rule.additionAllRule, tagsExist);

		// 作品タグとタグクラウドの共通タグを抽出
		var tagsFound = tagsExist.filter((existTag) => {
			return tagCloud.some((cloudTag) =>{
				return existTag === cloudTag;
			});
		});

		// 付与タグリストの生成
		var tagsAdded = [];
		applySomeRule(tagsExist, rule.patternRule, tagsAdded);
		applyAllRule(tagsExist, rule.patternAllRule, tagsAdded);

		// タグを消去する
		tagsFound = tagsFound.filter((foundTag) => { return tagsAdded.indexOf('-'+foundTag) === -1; });
		tagsAdded = tagsAdded.filter((tag) => { return !tag.match(/^-/); });

		// 重複の削除
		var uniqueAry = tagsFound.concat(tagsAdded).filter((elem, i, ary) => { return ary.indexOf(elem) === i; }).join(' ');

		// ブックマークタグを設定する
		input.value = uniqueAry;

		// タグのハイライトを表示させる
		var keyupEvent = document.createEvent('HTMLEvents');
		keyupEvent.initEvent('keyup', true, true);
		input.dispatchEvent(keyupEvent);

		// タグ付けが終わったら、背景を緑色にして補完がされたことを示す
		input.parentNode.style.backgroundColor = '#76B6E0';
		// それと入力欄にフォーカスする
		input.focus();
	}
};

function generateButtons() {
	// 設定ボタンの生成
	var settingsButton = document.createElement('button');
	settingsButton.className = '_button';
	settingsButton.style.marginLeft = '1em';
	settingsButton.id = 'autotag-settings-button';
	settingsButton.textContent = 'タグ自動化設定';
	settingsButton.addEventListener('click', toggleSettingsView, false);

	// タグ付けボタンの生成
	var autoTagButton = document.createElement('button');
	autoTagButton.className = '_button';
	autoTagButton.style.marginLeft = '0.25em';
	autoTagButton.id = 'autotag-button';
	autoTagButton.textContent = '上書きタグ付け';
	autoTagButton.addEventListener('click', () => {
		var input = document.querySelector('#input_tag');
		input.value = '';
		autoTag();
	}, false);

	var target = document.querySelector('div.recommend-tag > h1');
	target.parentElement.insertBefore(settingsButton, target.nextSibling);
	target.parentElement.insertBefore(autoTagButton, settingsButton.nextSibling);
};

(() => {
	window.addEventListener('PixivAutoTag.autoTag', () => autoTag() );
	window.addEventListener('PixivAutoTag.generateButtons', () => generateButtons() );

	window.addEventListener('beforeunload', (e) => {
		if (textareaUpdateFlag) {
			var confirmationMessage = "PixivAutoTag.user.js の設定が変更されています。破棄してもよろしいですか？";
			e.returnValue = confirmationMessage;
			return confirmationMessage;
		}
	});

	// 自動タグ付けの実行
	if (/member_illust.php/.test(location.href)) {
		var ul = document.querySelector('section.list-container.tag-container.work-tags-container > div > ul');
		function checkTagGenerated(fn) { (ul.childNodes.length > 0) ? window.setTimeout(autoTag, 750) : window.setTimeout(fn, 750, fn); };
		window.setTimeout(checkTagGenerated, 1250, checkTagGenerated);
		generateButtons();
	} else if (/bookmark_add/.test(location.href)) {
		window.setTimeout(autoTag, 750);
		generateButtons();
	}

})();
<|endoftext|>


<commit_before>/* global define */
define([
    'marionette',
    'app',
    'helpers/search',
    'helpers/syncStatus'
], function (Marionette, App) {
    'use strict';

    /**
     * Submodule which shows notebooks */
    var AppNotebooks = App.module('AppNotebook', {startWithParent: false}),
        executeAction,
        API;

    AppNotebooks.on('start', function () {
        App.mousetrap.API.restart();
        App.content.reset();
        App.log('AppNotebook is started');
    });

    AppNotebooks.on('stop', function () {
        App.log('AppNotebook is stoped');
    });

    /**
     * The Router
     */
    AppNotebooks.Router = Marionette.AppRouter.extend({
        appRoutes: {
            // Notebooks
            'notebooks'     : 'listNotebooks',
            'notebooks/add' : 'addNotebook',
            'notebooks/edit/:id' : 'editNotebook',
            'notebooks/remove/:id' : 'removeNotebook',

            // Tags
            'tags/add'      : 'addTag',
            'tags/edit/:id' : 'editTag',
            'tags/remove/:id': 'removeTag'
        }
    });

    /**
     * Start application
     */
    executeAction = function (action, args) {
        App.startSubApp('AppNotebook');
        action(args);
    };

    /**
     * Controller
     */
    API = {
        /**
         * Methods for notebooks
         */
        // Shows list of notebooks and tags
        listNotebooks: function () {
            require(['apps/notebooks/list/controller'], function (List) {
                executeAction(new List().list);
            });
        },

        // Create notebook
        addNotebook: function () {
            require(['apps/notebooks/notebooksForm/controller'], function (Form) {
                executeAction(new Form().addForm);
            });
        },

        // Edit notebook
        editNotebook: function (id) {
            require(['apps/notebooks/notebooksForm/controller'], function (Form) {
                executeAction(new Form().editForm, {id: id});
            });
        },

        // Delete notebook
        removeNotebook: function (id) {
            require(['apps/notebooks/remove/notebook'], function (Controller) {
                executeAction(new Controller().start, {id: id});
            });
        },

        /**
         * Methods for tags
         */
        addTag: function () {
            require(['apps/notebooks/tagsForm/controller'], function (Form) {
                executeAction(new Form().addForm);
            });
        },

        editTag: function (id) {
            require(['apps/notebooks/tagsForm/controller'], function (Form) {
                executeAction(new Form().editForm, {id: id});
            });
        },

        removeTag: function (id) {
            require(['apps/notebooks/remove/tag'], function (Controller) {
                executeAction(new Controller().start, {id: id});
            });
        }
    };

    // Add notebook
    AppNotebooks.on('showForm', function () {
        App.navigate('/notebooks/add', true);
    });

    /**
     * Register the router
     */
    App.addInitializer(function() {
        new AppNotebooks.Router({
            controller: API
        });
    });

    return AppNotebooks;
});
<commit_msg>show sidebar in #/notebooks page in small devices<commit_after>/* global define */
define([
    'marionette',
    'app',
    'jquery',
    'helpers/search',
    'helpers/syncStatus'
], function (Marionette, App, $) {
    'use strict';

    /**
     * Submodule which shows notebooks */
    var AppNotebooks = App.module('AppNotebook', {startWithParent: false}),
        executeAction,
        API;

    AppNotebooks.on('start', function () {
        App.mousetrap.API.restart();
        App.content.reset();
        $(App.content.el).removeClass('active-row');
        App.log('AppNotebook is started');
    });

    AppNotebooks.on('stop', function () {
        App.log('AppNotebook is stoped');
    });

    /**
     * The Router
     */
    AppNotebooks.Router = Marionette.AppRouter.extend({
        appRoutes: {
            // Notebooks
            'notebooks'     : 'listNotebooks',
            'notebooks/add' : 'addNotebook',
            'notebooks/edit/:id' : 'editNotebook',
            'notebooks/remove/:id' : 'removeNotebook',

            // Tags
            'tags/add'      : 'addTag',
            'tags/edit/:id' : 'editTag',
            'tags/remove/:id': 'removeTag'
        }
    });

    /**
     * Start application
     */
    executeAction = function (action, args) {
        App.startSubApp('AppNotebook');
        action(args);
    };

    /**
     * Controller
     */
    API = {
        /**
         * Methods for notebooks
         */
        // Shows list of notebooks and tags
        listNotebooks: function () {
            require(['apps/notebooks/list/controller'], function (List) {
                executeAction(new List().list);
            });
        },

        // Create notebook
        addNotebook: function () {
            require(['apps/notebooks/notebooksForm/controller'], function (Form) {
                executeAction(new Form().addForm);
            });
        },

        // Edit notebook
        editNotebook: function (id) {
            require(['apps/notebooks/notebooksForm/controller'], function (Form) {
                executeAction(new Form().editForm, {id: id});
            });
        },

        // Delete notebook
        removeNotebook: function (id) {
            require(['apps/notebooks/remove/notebook'], function (Controller) {
                executeAction(new Controller().start, {id: id});
            });
        },

        /**
         * Methods for tags
         */
        addTag: function () {
            require(['apps/notebooks/tagsForm/controller'], function (Form) {
                executeAction(new Form().addForm);
            });
        },

        editTag: function (id) {
            require(['apps/notebooks/tagsForm/controller'], function (Form) {
                executeAction(new Form().editForm, {id: id});
            });
        },

        removeTag: function (id) {
            require(['apps/notebooks/remove/tag'], function (Controller) {
                executeAction(new Controller().start, {id: id});
            });
        }
    };

    // Add notebook
    AppNotebooks.on('showForm', function () {
        App.navigate('/notebooks/add', true);
    });

    /**
     * Register the router
     */
    App.addInitializer(function() {
        new AppNotebooks.Router({
            controller: API
        });
    });

    return AppNotebooks;
});
<|endoftext|>


<commit_before>(function () {
  'use strict';
  /* jshint validthis:true */
  angular
    .module('porttare.directives')
    .directive('productCounter', productCounter);

  function productCounter() {
    var directive = {
      restrict: 'E',
      templateUrl: 'templates/directives/product-counter/product-counter.html',
      scope: {
        options: '='
      },
      controller: productCounterController,
      controllerAs: 'pcVm',
      bindToController: true
    };

    return directive;
  }

  function productCounterController() {
    var pcVm = this,
      options = {},
      centValue = 0.01,
      limit = 0,
      actions = {
        subtract: 'subtract',
        add: 'add'
      };

    pcVm.handleClickMinus = handleClickMinus;
    pcVm.handleClickPlus = handleClickPlus;

    init();

    pcVm.itemsCount = options.cantidad;
    pcVm.priceTotalCents = getTotal();

    function processData(option) {
      if (isInRange(pcVm.itemsCount)) {
        if (option === actions.subtract) {
          if (pcVm.itemsCount !== limit) {
            changeCounter(option);
          }
        } else {
          changeCounter(option);
        }
      }
    }

    function changeCounter(option) {
      if (option === actions.add) {
        pcVm.itemsCount++;
      } else if (option === actions.subtract) {
        pcVm.itemsCount--;
      }
      pcVm.priceTotalCents = getTotal();
      if (options.onChangeValue && angular.isFunction(options.onChangeValue)) {
        var data = {
          itemsCount: pcVm.itemsCount,
          priceTotalCents: pcVm.priceTotalCents
        };
        options.onChangeValue(data);
      }
    }

    function handleClickMinus() {
      runAction('onClickMinus');
      processData(actions.subtract);
    }

    function handleClickPlus() {
      runAction('onClickPlus');
      processData(actions.add);
    }

    function runAction(name) {
      if (options[name] && angular.isFunction(options[name])) {
        options[name]();
      }
    }

    function init() {
      var defaultOptions = {
        priceCents: 0,
        onClickMinus: null,
        onClickPlus: null,
        onChangeValue: null,
        cantidad: 0
      };

      if (!isValidNumber(pcVm.options.priceCents)) {
        throw 'Invalid value for option \'priceCents\'';
      }

      options = angular.merge({}, defaultOptions, pcVm.options);
    }

    function isValidNumber(value) {
      return angular.isNumber(value) && Number.isInteger(value);
    }

    function isInRange(value) {
      return value >= limit;
    }

    function getTotal() {
      return pcVm.itemsCount * (options.priceCents);
    }
  }
})();
<commit_msg>jshint<commit_after>(function () {
  'use strict';
  /* jshint validthis:true */
  angular
    .module('porttare.directives')
    .directive('productCounter', productCounter);

  function productCounter() {
    var directive = {
      restrict: 'E',
      templateUrl: 'templates/directives/product-counter/product-counter.html',
      scope: {
        options: '='
      },
      controller: productCounterController,
      controllerAs: 'pcVm',
      bindToController: true
    };

    return directive;
  }

  function productCounterController() {
    var pcVm = this,
      options = {},
      limit = 0,
      actions = {
        subtract: 'subtract',
        add: 'add'
      };

    pcVm.handleClickMinus = handleClickMinus;
    pcVm.handleClickPlus = handleClickPlus;

    init();

    pcVm.itemsCount = options.cantidad;
    pcVm.priceTotalCents = getTotal();

    function processData(option) {
      if (isInRange(pcVm.itemsCount)) {
        if (option === actions.subtract) {
          if (pcVm.itemsCount !== limit) {
            changeCounter(option);
          }
        } else {
          changeCounter(option);
        }
      }
    }

    function changeCounter(option) {
      if (option === actions.add) {
        pcVm.itemsCount++;
      } else if (option === actions.subtract) {
        pcVm.itemsCount--;
      }
      pcVm.priceTotalCents = getTotal();
      if (options.onChangeValue && angular.isFunction(options.onChangeValue)) {
        var data = {
          itemsCount: pcVm.itemsCount,
          priceTotalCents: pcVm.priceTotalCents
        };
        options.onChangeValue(data);
      }
    }

    function handleClickMinus() {
      runAction('onClickMinus');
      processData(actions.subtract);
    }

    function handleClickPlus() {
      runAction('onClickPlus');
      processData(actions.add);
    }

    function runAction(name) {
      if (options[name] && angular.isFunction(options[name])) {
        options[name]();
      }
    }

    function init() {
      var defaultOptions = {
        priceCents: 0,
        onClickMinus: null,
        onClickPlus: null,
        onChangeValue: null,
        cantidad: 0
      };

      if (!isValidNumber(pcVm.options.priceCents)) {
        throw 'Invalid value for option \'priceCents\'';
      }

      options = angular.merge({}, defaultOptions, pcVm.options);
    }

    function isValidNumber(value) {
      return angular.isNumber(value) && Number.isInteger(value);
    }

    function isInRange(value) {
      return value >= limit;
    }

    function getTotal() {
      return pcVm.itemsCount * (options.priceCents);
    }
  }
})();
<|endoftext|>


<commit_before>'use strict';

var userController = require('./user_controller');
var debug = require('debug')('docker-chat:socketio');

var MESSAGE_TYPE = {
    control: 'control',
    message: 'message',
    typing: 'typing'
};

exports.init = function (io) {
    var connectedUsers = []; // Array with connected users' ids
    var socketMap = {}; // Map with connected user and sockets

    // start listen with socket.io
    debug('Socket Initialized');

    io.on('connection', function (socket) {
        debug('Socket Connected');

        /*
         When the user sends a chat message, publish it to everyone (including myself)
         Notice that we are getting user's name from session.
         */
        socket.on('chat', function (data) {
            debug(data);
            var msg = JSON.parse(data);
            userController.addMsg(socket.handshake.session.user.name, msg, function (sendedMessage) {
                broadcastMessage('chat', MESSAGE_TYPE.message, sendedMessage);
            });
        });

        /*
         When a user joins the channel, publish it to everyone (including myself) using
         Redis' 'pub' client we created earlier.
         Notice that we are getting user's name from session.
         */
        socket.on('user', function (myName) {
            var user = socket.handshake.session.user;
            if (connectedUsers.indexOf(user._id) < 0) {
                connectedUsers.push(user._id);
                debug(`New User ${myName}:${socket.handshake.session.user.name} CONNECTED `);
                debug(`Socket User session ${JSON.stringify(socket.handshake.session.user)}`);
            }
            socketMap[socket.id] = user._id;

            debug("Number of users: " + connectedUsers.length);
            broadcastMessage('onof', MESSAGE_TYPE.control, `${connectedUsers.length}`);
            broadcastMessage('chat', MESSAGE_TYPE.control, `${user.name} joined the chat`);

            userController.getUser(user.name, function (foundUser) {
                if (foundUser && foundUser.messages) {
                    foundUser.messages.forEach(function (msg) {
                        // send them just to me
                        socket.emit('old message', msg);
                    });
                }
            });

        });

        socket.on('typing', function (isTyping) {
            var user = socket.handshake.session.user;
            //console.log('User: ' + name + " is typing " + isTyping);
            broadcastMessage('typing', MESSAGE_TYPE.typing, isTyping);
            //socket.broadcast.emit('typing', isTyping, name);
        });

        socket.on('disconnect', function () {
            if (!socketMap || !socketMap[socket.id]) {
                return;
            }

            debug('User ' + socketMap[socket.id] + ' DISCONNECTED');
            debug(connectedUsers.indexOf(socketMap[socket.id]) >= 0);
            var opennedSessions = 0;

            for ( var socketId in socketMap) {
                if (socketMap[socket.id] == socketMap[socketId]) {
                    opennedSessions++;
                }
                if (opennedSessions >= 2) {
                    break;
                }
            }
            if (opennedSessions <= 1) {
                connectedUsers.splice(connectedUsers.indexOf(socketMap[socket.id]), 1);
            }
            broadcastMessage('chat', MESSAGE_TYPE.control, `${socket.handshake.session.user.name} left the chat`);

            delete socketMap[socket.id];

            debug("Number of users: " + connectedUsers.length);
            broadcastMessage('onof', MESSAGE_TYPE.control, (connectedUsers.length));
        });

        function broadcastMessage(channel, action, msg) {
            var message = getFormatedMessage(action, msg, socket);
            debug(`Broadcast message in channel: ${channel}: ${JSON.stringify(message)}`);
            io.emit(channel, message);
        }

        function getFormatedMessage(action, msg) {
            return JSON.stringify({
                action: action,
                user: socket.handshake.session.user,
                msg: msg
            });
        }
    });
}

// socket.on('chat message', function (msg) {
//     console.log("message from: " + socket.id + " - " + app.socketMap[socket.id]);
//     userController.addMsg(app.socketMap[socket.id], msg, function (sendedMessage) {
//         // console.log(sendedMessage);
//         socket.broadcast.emit('chat message', sendedMessage, app.socketMap[socket.id]);
//     });
// });

// socket.on('typing', function (isTyping, name) {
//     //console.log('User: ' + name + " is typing " + isTyping);
//     socket.broadcast.emit('typing', isTyping, name);
// });

// socket.on('new user', function (user) {
//     app.socketMap[socket.id] = user;

//     if (connectedUsers.indexOf(user) < 0) {
//         connectedUsers.push(user);
//         console.log("New User CONNECTED ")

//     }
//     connectedUsers[connectedUsers.indexOf(user)]
//     console.log('User: ' + app.socketMap[socket.id] + " CONNECTED");

//     console.log("Number of users: " + connectedUsers.length);
//     app.io.emit('connection on off', (connectedUsers.length));
//     userController.getUser(user, function (foundUser) {
//         if (foundUser && foundUser.messages) {
//             foundUser.messages.forEach(function (msg) {
//                 // send them just to me
//                 app.io.to(socket.id).emit('old message', msg, app.socketMap[socket.id]);
//             });
//         }
//     });
// });

// socket.on('disconnect', function () {
//     console.log('User ' + app.socketMap[socket.id] + ' DISCONNECTED');
//     console.log(connectedUsers.indexOf(app.socketMap[socket.id]) >= 0);
//     opennedSessions = 0;
//     for (socketId in app.socketMap) {
//         if (app.socketMap[socket.id] == app.socketMap[socketId]) {
//             opennedSessions++;
//         }
//         if (opennedSessions >= 2) {
//             break;
//         }
//     }
//     if (opennedSessions <= 1) {
//         connectedUsers.splice(connectedUsers.indexOf(app.socketMap[socket.id]), 1);
//     }

//     delete app.socketMap[socket.id];

//     console.log("Number of users: " + connectedUsers.length);
//     app.io.emit('connection on off', (connectedUsers.length));
// });<commit_msg>updt: fix all socket problems, add client logic, add socket-io-express session, add debug logs<commit_after>'use strict';

var userController = require('./user_controller');
var debug = require('debug')('docker-chat:socketio');

var MESSAGE_TYPE = {
    control: 'control',
    message: 'message',
    typing: 'typing'
};

exports.init = function (io) {
    var connectedUsers = []; // Array with connected users' ids
    var socketMap = {}; // Map with connected user and sockets

    // start listen with socket.io
    debug('Socket Initialized');

    io.on('connection', function (socket) {
        debug('Socket Connected');

        /*
         When the user sends a chat message, publish it to everyone (including myself)
         Notice that we are getting user's name from session.
         */
        socket.on('chat', function (data) {
            debug(data);
            var msg = JSON.parse(data);
            userController.addMsg(socket.handshake.session.user.name, msg, function (sendedMessage) {
                broadcastMessage('chat', MESSAGE_TYPE.message, sendedMessage);
            });
        });

        /*
         When a user joins the channel, publish it to everyone (including myself)
         Notice that we are getting user's name from session.
         */
        socket.on('user', function (myName) {
            var user = socket.handshake.session.user;
            if (connectedUsers.indexOf(user._id) < 0) {
                connectedUsers.push(user._id);
                debug(`New User ${myName}:${socket.handshake.session.user.name} CONNECTED `);
                debug(`Socket User session ${JSON.stringify(socket.handshake.session.user)}`);
            }
            socketMap[socket.id] = user._id;

            debug("Number of users: " + connectedUsers.length);
            broadcastMessage('onof', MESSAGE_TYPE.control, `${connectedUsers.length}`);
            broadcastMessage('chat', MESSAGE_TYPE.control, `${user.name} joined the chat`);

            userController.getUser(user.name, function (foundUser) {
                if (foundUser && foundUser.messages) {
                    foundUser.messages.forEach(function (msg) {
                        // send them just to me
                        socket.emit('old message', msg);
                    });
                }
            });

        });

        socket.on('typing', function (isTyping) {
            var user = socket.handshake.session.user;
            //console.log('User: ' + name + " is typing " + isTyping);
            broadcastMessage('typing', MESSAGE_TYPE.typing, isTyping);
            //socket.broadcast.emit('typing', isTyping, name);
        });

        socket.on('disconnect', function () {
            if (!socketMap || !socketMap[socket.id]) {
                return;
            }

            debug('User ' + socketMap[socket.id] + ' DISCONNECTED');
            debug(connectedUsers.indexOf(socketMap[socket.id]) >= 0);
            var opennedSessions = 0;

            for ( var socketId in socketMap) {
                if (socketMap[socket.id] == socketMap[socketId]) {
                    opennedSessions++;
                }
                if (opennedSessions >= 2) {
                    break;
                }
            }
            if (opennedSessions <= 1) {
                connectedUsers.splice(connectedUsers.indexOf(socketMap[socket.id]), 1);
            }
            broadcastMessage('chat', MESSAGE_TYPE.control, `${socket.handshake.session.user.name} left the chat`);

            delete socketMap[socket.id];

            debug("Number of users: " + connectedUsers.length);
            broadcastMessage('onof', MESSAGE_TYPE.control, (connectedUsers.length));
        });

        function broadcastMessage(channel, action, msg) {
            var message = getFormatedMessage(action, msg, socket);
            debug(`Broadcast message in channel: ${channel}: ${JSON.stringify(message)}`);
            io.emit(channel, message);
        }

        function getFormatedMessage(action, msg) {
            return JSON.stringify({
                action: action,
                user: socket.handshake.session.user,
                msg: msg
            });
        }
    });
}
<|endoftext|>


<commit_before>game.PlayerEntity = me.Entity.extend({
    init: function(x, y, settings) {
        this._super(me.Entity, 'init', [x, y, {
                image: "player", //loads image named "player" (named in resources.js)
                width: 64, //Sets width to 64. //:D :D :D :D :D :D :D :D
                height: 64, //Sets height to 64.
                spritewidth: "64", //draws from sprite and sets to 64
                spriteheight: "64",
                getShape: function() {
                    return(new me.Rect(0, 0, 64, 64)).toPolygon();
                }
            }]);

        this.body.setVelocity(5, 20); //sets movement speed
        this.facing = "right";
        //keeps track of which direction your chharacter is going :D
        me.game.viewport.follow(this.pos, me.game.viewport.AXIS.BOTH);

        this.renderable.addAnimation("idle", [78]);
        this.renderable.addAnimation("walk", [117, 118, 119, 120, 121, 122, 123, 124, 125], 80);
        this.renderable.addAnimation("attack", [65, 66, 67, 68, 69, 70, 71, 72], 80);

        this.renderable.setCurrentAnimation("idle");

    },
    update: function(delta) {
        if (me.input.isKeyPressed("right")) {
            //sets position of x by adding the velocity defined aboove in set velocity and then
            //multiplying it by me.timer.tick
            this.body.vel.x += this.body.accel.x * me.timer.tick; //me.timer.tick makes movement look smooth
            this.facing = "right";
            this.flipX(true); //Se  ts orc to walk to the right instead of the left
        } else if (me.input.isKeyPressed("left")) {
            this.facing = "left";
            this.body.vel.x -= this.body.accel.x * me.timer.tick;
            this.flipX(false);
        } else {
            this.body.vel.x = 0;
        }

        if (me.input.isKeyPressed("jump") && !this.body.jumping && !this.body.falling) {
            this.body.jumping = true;
            this.body.vel.y -= this.body.accel.y * me.timer.tick;
        }

        if (me.input.isKeyPressed("attack")) {
            if (!this.renderable.isCurrentAnimation("attack")) {
                //sets current animation to attakc and once that is done
                //it goes back to the idle animation
                this.renderable.setCurrentAnimation("attack", "idle");
                //makes it so that next time we start this sequence of animation we start off
                //from the begining of the animation  instead of where we left off
                this.renderable.setAnimationFrame();
            }
        }

        else if (this.body.vel.x !== 0) {
            if (!this.renderable.isCurrentAnimation("walk")) {
                this.renderable.setCurrentAnimation("walk"); //says that  if not using walking animation, use walking animation
            }
        } else {
            this.renderable.setCurrentAnimation("idle");
        }

        me.collision.check(this, true, this.collideHandler.bind(this), true);
        this.body.update(delta);

        this._super(me.Entity, "update", [delta]);

        return true;
    },

    collideHandler: function(response) {
        if(response.b.type==='EnemyBaseEntity') {
            var ydif = this.pos.y - response.b.pos.y;
            var xdif = this.pos.x
        }
    }
});

game.PlayerBaseEntity = me.Entity.extend({
    init: function(x, y, settings) {
        this._super(me.Entity, 'init', [x, y, {
                image: "tower",
                width: 100,
                height: 100,
                spritewidth: "100",
                spriteheight: "100",
                getShape: function() {
                    return (new me.Rect(0, 0, 100, 70)).toPolygon();
                }

            }]);

        this.broken = false; //states the base is not broken when started
        this.health = 10; //makes healtth for base 10
        this.alwaysUpdate = true; //states that even if the base is not within screen sight, it is still constantly updating
        this.body.onCollision = this.onCollision.bind(this); //checks for collisions within the PlayerEntityBase
        this.type = "PlayerBaseEntity";

        this.renderable.addAnimation("idle", [0]);
        this.renderable.addAnimation("broken", [1]);
        this.renderable.setCurrentAnimation("idle");

    },
    update: function(delta) {
        if (this.health <= 0) { //creates update function used for if statement
            this.broken = true; //states that if health is less than or equal to 0, then we are dead.
            this.renderable.setCurrentAnimation("broken");
        }
        this.body.update(delta);

        this._super(me.Entity, "update", [delta]);
        return true;
    },
    onCollision: function() {

    }

});

game.EnemyBaseEntity = me.Entity.extend({
    init: function(x, y, settings) {
        this._super(me.Entity, 'init', [x, y, {
                image: "tower",
                width: 100,
                height: 100,
                spritewidth: "100",
                spriteheight: "100",
                getShape: function() {
                    return (new me.Rect(0, 0, 100, 70)).toPolygon();
                }

            }]);

        this.broken = false; //states the base is not broken when started
        this.health = 10; //makes healtth f or base 10
        this.alwaysUpdate = true; //states that even if the base is not within screen sight, it is still constantly updating
        this.body.onCollision = this.onCollision.bind(this); //checks for collisions within the PlayerEntityBase

        this.type = "EnemyBaseEntity";

        this.renderable.addAnimation("idle", [0]);
        this.renderable.addAnimation("broken", [1]);
        this.renderable.setCurrentAnimation("idle");

    },
    update: function(delta) {
        if (this.health <= 0) { //creates update function used for if statement
            this.broken = true; //states that if health is less than or equal to 0, then we are dead.
            this.renderable.setCurrentAnimation("broken");
        }
        this.body.update(delta);

        this._super(me.Entity, "update", [delta]);
        return true;
    },
    onCollision: function() {

    }

});<commit_msg>Added collisions.<commit_after>game.PlayerEntity = me.Entity.extend({
    init: function(x, y, settings) {
        this._super(me.Entity, 'init', [x, y, {
                image: "player", //loads image named "player" (named in resources.js)
                width: 64, //Sets width to 64. //:D :D :D :D :D :D :D :D
                height: 64, //Sets height to 64.
                spritewidth: "64", //draws from sprite and sets to 64
                spriteheight: "64",
                getShape: function() {
                    return(new me.Rect(0, 0, 64, 64)).toPolygon();
                }
            }]);

        this.body.setVelocity(5, 20); //sets movement speed
        this.facing = "right";
        //keeps track of which direction your chharacter is going :D
        this.now = new Date().getTime();
        this.lastHit = this.now();
        this.lastAttack = new Date().getTime();
        me.game.viewport.follow(this.pos, me.game.viewport.AXIS.BOTH);

        this.renderable.addAnimation("idle", [78]);
        this.renderable.addAnimation("walk", [117, 118, 119, 120, 121, 122, 123, 124, 125], 80);
        this.renderable.addAnimation("attack", [65, 66, 67, 68, 69, 70, 71, 72], 80);

        this.renderable.setCurrentAnimation("idle");

    },
    update: function(delta) {
        this.now = new Date().getTime();
        if (me.input.isKeyPressed("right")) {
            //sets position of x by adding the velocity defined aboove in set velocity and then
            //multiplying it by me.timer.tick
            this.body.vel.x += this.body.accel.x * me.timer.tick; //me.timer.tick makes movement look smooth
            this.facing = "right";
            this.flipX(true); //Se  ts orc to walk to the right instead of the left
        } else if (me.input.isKeyPressed("left")) {
            this.facing = "left";
            this.body.vel.x -= this.body.accel.x * me.timer.tick;
            this.flipX(false);
        } else {
            this.body.vel.x = 0;
        }

        if (me.input.isKeyPressed("jump") && !this.body.jumping && !this.body.falling) {
            this.body.jumping = true;
            this.body.vel.y -= this.body.accel.y * me.timer.tick;
        }

        if (me.input.isKeyPressed("attack")) {
            if (!this.renderable.isCurrentAnimation("attack")) {
                //sets current animation to attakc and once that is done
                //it goes back to the idle animation
                this.renderable.setCurrentAnimation("attack", "idle");
                //makes it so that next time we start this sequence of animation we start off
                //from the begining of the animation  instead of where we left off
                this.renderable.setAnimationFrame();
            }
        }

        else if (this.body.vel.x !== 0 && !this.renderable.isCurrentAnimation("attack")) {
            if (!this.renderable.isCurrentAnimation("walk")) {
                this.renderable.setCurrentAnimation("walk"); //says that  if not using walking animation, use walking animation
            }
        } else if(!this.renderable.isCurrentAnimation("attack")) {
            this.renderable.setCurrentAnimation("idle");
        }

        me.collision.check(this, true, this.collideHandler.bind(this), true);
        this.body.update(delta);

        this._super(me.Entity, "update", [delta]);

        return true;
    },

    collideHandler: function(response) {
        if(response.b.type==='EnemyBaseEntity') {
            var ydif = this.pos.y - response.b.pos.y;
            var xdif = this.pos.x - response.b.pos.x;

            if(ydif<-40 && xdif< 70 && xdif>-35) {
                this.body.falling = false;
                this.body.vel.y = -1;
            }

            else if(xdif>-35 && this.facing==='right' && (xdif<0)) {
                this.body.vel.x = 0;
                this.pos.x = this.pos.x - 1;
            }else if(xdif<60 && this.facing==='left' && xdif>0) {
                this.body.vel.x = 0;
                this.pos.x = this.pos.x +1;
            }

            if(this.renderable.isCurrentAnimation("attack"))
                response.b.loseHealth();

        }
    }
});

game.PlayerBaseEntity = me.Entity.extend({
    init: function(x, y, settings) {
        this._super(me.Entity, 'init', [x, y, {
                image: "tower",
                width: 100,
                height: 100,
                spritewidth: "100",
                spriteheight: "100",
                getShape: function() {
                    return (new me.Rect(0, 0, 100, 70)).toPolygon();
                }

            }]);

        this.broken = false; //states the base is not broken when started
        this.health = 10; //makes healtth for base 10
        this.alwaysUpdate = true; //states that even if the base is not within screen sight, it is still constantly updating
        this.body.onCollision = this.onCollision.bind(this); //checks for collisions within the PlayerEntityBase
        this.type = "PlayerBaseEntity";

        this.renderable.addAnimation("idle", [0]);
        this.renderable.addAnimation("broken", [1]);
        this.renderable.setCurrentAnimation("idle");

    },
    update: function(delta) {
        if (this.health <= 0) { //creates update function used for if statement
            this.broken = true; //states that if health is less than or equal to 0, then we are dead.
            this.renderable.setCurrentAnimation("broken");
        }
        this.body.update(delta);

        this._super(me.Entity, "update", [delta]);
        return true;
    },
    onCollision: function() {

    }

});

game.EnemyBaseEntity = me.Entity.extend({
    init: function(x, y, settings) {
        this._super(me.Entity, 'init', [x, y, {
                image: "tower",
                width: 100,
                height: 100,
                spritewidth: "100",
                spriteheight: "100",
                getShape: function() {
                    return (new me.Rect(0, 0, 100, 70)).toPolygon();
                }

            }]);

        this.broken = false; //states the base is not broken when started
        this.health = 10; //makes healtth f or base 10
        this.alwaysUpdate = true; //states that even if the base is not within screen sight, it is still constantly updating
        this.body.onCollision = this.onCollision.bind(this); //checks for collisions within the PlayerEntityBase

        this.type = "EnemyBaseEntity";

        this.renderable.addAnimation("idle", [0]);
        this.renderable.addAnimation("broken", [1]);
        this.renderable.setCurrentAnimation("idle");

    },
    update: function(delta) {
        if (this.health <= 0) { //creates update function used for if statement
            this.broken = true; //states that if health is less than or equal to 0, then we are dead.
            this.renderable.setCurrentAnimation("broken");
        }
        this.body.update(delta);

        this._super(me.Entity, "update", [delta]);
        return true;
    },
    onCollision: function() {

    },

    loseHealth: function() {
        this.health--;
    }

});<|endoftext|>


<commit_before>import React from 'react';
import Relay from 'react-relay';
import {BernieText, BernieColors} from './styles/bernie-css';
import {Paper, List, ListItem, FlatButton} from 'material-ui';
import SideBarLayout from './SideBarLayout';
import Survey from './Survey'
import moment from 'moment';
import yup from 'yup'
import GCForm from './forms/GCForm';
import Form from 'react-formal';

export class CallAssignment extends React.Component {
  styles = {
    assignmentBar: {
      backgroundColor: BernieColors.lightGray,
      marginTop: 15,
      marginLeft: 'auto',
      marginRight: 'auto',
      marginBottom: 15,
      paddingTop: 15,
      paddingLeft: 15,
      paddingRight: 15,
      paddingBottom: 15,
      color: BernieColors.darkGray,
      fontSize: '1em',
      width: 'auto',
      maxWidth: 720
    },
    callAssignmentQuestions: {
      fontSize: '1em',
      marginBottom: 15,
    },
    surveyFrame: {
      borderTop: 'solid 1px ' + BernieColors.lightGray,
    },
    questions: {
      paddingTop: 15,
      maxWidth: 720,
      marginLeft: 'auto',
      marginRight: 'auto'
    },
    submitButton: {

    }
  }

  state = {
    completed: true,
    reasonNotCompleted: null,
    leftVoicemail: null
  }

  notCompletedReasons =
  {
    'NO_PICKUP' : 'Did not pick up',
    'CALL_BACK' : 'Busy/call back later',
    'NOT_INTERESTED' : 'Not interested in talking',
    'OTHER_LANGUAGE' : 'Spoke different language',
    'WRONG_NUMBER' : 'Wrong number',
    'DISCONNECTED_NUMBER' : 'Disconnected number'
  }

  formSchema = yup
    .object({
      completed: yup
        .boolean()
        .required(),
      reasonNotCompleted: yup
        .string()
        .nullable()
        .test(
          'not-completed-reasons-required',
          'Fill out why you were unable to complete the call',
          function(value) {
            if (this.parent.completed)
              return true;
            else
              return value !== null
          }
        ),
      leftVoicemail: yup
        .boolean()
        .nullable()
        .test(
          'left-voicemail-required',
          'Let us know if you left a voicemail',
          function(value) {
            if (this.parent.completed)
              return true
            else
              return value !== null
          }
        )
    })

  intervieweeName() {
    let interviewee = this.props.currentUser.intervieweeForCallAssignment;
    let name = ''
    let keys = ['prefix', 'firstName', 'middleName', 'lastName', 'suffix']
    keys.forEach((key) => {
      if (interviewee[key])
        name = name + ' ' + interviewee[key]
    })
    name = name.trim();
    return name === '' ? 'Unknown name' : name
  }

  formatPhoneNumber(number) {
    return '(' + number.slice(0, 3) + ') ' + number.slice(3, 6) + '-' + number.slice(7)
  }

  renderIntervieweeInfo() {
    let interviewee = this.props.currentUser.intervieweeForCallAssignment;
    let name = this.intervieweeName();
    let number = this.formatPhoneNumber(interviewee.phone)

    let sideBar = (

        {name}

        {number}

    )

    let location = interviewee.address.city + ', ' + interviewee.address.state + ' ' + interviewee.address.zip

    let content = (

        Location: {location}

        Local Time: {moment().utcOffset(interviewee.address.localTime).format('h:mm a')}

    )

    return (
      <SideBarLayout
        content={content}
        sideBar={sideBar}
        sideBarStyle={{
          width: 300,
        }}
        contentViewStyle={{
          marginLeft: 50,
        }}
      />
    )
  }

  render() {
    if (this.props.currentUser.intervieweeForCallAssignment === null)
      return (

            All done!

            We have no one left for you to call right now, but try again tomorrow.

      )
    let submitHandler = (formValue) => {
      if (this.state.completed)
        this.refs.survey.refs.component.submit()
    }

    let survey = (

        <Survey ref='survey' survey={this.props.callAssignment.survey} initialValues={{'email' : this.props.currentUser.intervieweeForCallAssignment.email}} />

    )

    let notCompletedQuestions =

    if (!this.state.completed)
      notCompletedQuestions = (

          <Form.Field
            name='reasonNotCompleted'
            type='select'
            choices={this.notCompletedReasons}
            label='Why not?'
            />

          <Form.Field
            name='leftVoicemail'
            label='Did you leave a voicemail?'
          />

      )
    submitHandler = submitHandler.bind(this)
    return (

        <Paper
          style={this.styles.assignmentBar}
        >
          {this.renderIntervieweeInfo()}
        </Paper>

          <GCForm
            schema={this.formSchema}
            onSubmit={submitHandler}
            value={this.state}
            onChange={(formValue) => {
              this.setState(formValue)
            }}
          >

              <Form.Field
                name='completed'
                label='Were you able to complete the call?'
              />
              {notCompletedQuestions}

            {survey}

              <Form.Button type='submit' label='Submit and on to the person!' fullWidth={true} style={this.styles.submitButton}/>

          </GCForm>

    );
  }
}

export default Relay.createContainer(CallAssignment, {
  initialVariables: { id: null },
  fragments: {
    callAssignment: () => Relay.QL`
      fragment on CallAssignment {
        id
        name
        survey {
          ${Survey.getFragment('survey')}
        }
      }
    `,
    currentUser: () => Relay.QL`
      fragment on User {
        id
        intervieweeForCallAssignment(callAssignmentId:$id) {
          prefix
          firstName
          middleName
          lastName
          suffix
          gender
          birthDate
          title
          employer
          occupation
          phone
          email
          address {
            city
            state
            zip
            localTime
          }
        }
      }
    `
  }
});<commit_msg>Reload on successful submission<commit_after>import React from 'react';
import Relay from 'react-relay';
import {BernieText, BernieColors} from './styles/bernie-css';
import {Paper, List, ListItem, FlatButton} from 'material-ui';
import SideBarLayout from './SideBarLayout';
import Survey from './Survey'
import moment from 'moment';
import yup from 'yup'
import GCForm from './forms/GCForm';
import Form from 'react-formal';

export class CallAssignment extends React.Component {
  styles = {
    assignmentBar: {
      backgroundColor: BernieColors.lightGray,
      marginTop: 15,
      marginLeft: 'auto',
      marginRight: 'auto',
      marginBottom: 15,
      paddingTop: 15,
      paddingLeft: 15,
      paddingRight: 15,
      paddingBottom: 15,
      color: BernieColors.darkGray,
      fontSize: '1em',
      width: 'auto',
      maxWidth: 720
    },
    callAssignmentQuestions: {
      fontSize: '1em',
      marginBottom: 15,
    },
    surveyFrame: {
      borderTop: 'solid 1px ' + BernieColors.lightGray,
    },
    questions: {
      paddingTop: 15,
      maxWidth: 720,
      marginLeft: 'auto',
      marginRight: 'auto'
    },
    submitButton: {

    }
  }

  state = {
    completed: true,
    reasonNotCompleted: null,
    leftVoicemail: null
  }

  notCompletedReasons =
  {
    'NO_PICKUP' : 'Did not pick up',
    'CALL_BACK' : 'Busy/call back later',
    'NOT_INTERESTED' : 'Not interested in talking',
    'OTHER_LANGUAGE' : 'Spoke different language',
    'WRONG_NUMBER' : 'Wrong number',
    'DISCONNECTED_NUMBER' : 'Disconnected number'
  }

  formSchema = yup
    .object({
      completed: yup
        .boolean()
        .required(),
      reasonNotCompleted: yup
        .string()
        .nullable()
        .test(
          'not-completed-reasons-required',
          'Fill out why you were unable to complete the call',
          function(value) {
            if (this.parent.completed)
              return true;
            else
              return value !== null
          }
        ),
      leftVoicemail: yup
        .boolean()
        .nullable()
        .test(
          'left-voicemail-required',
          'Let us know if you left a voicemail',
          function(value) {
            if (this.parent.completed)
              return true
            else
              return value !== null
          }
        )
    })

  intervieweeName() {
    let interviewee = this.props.currentUser.intervieweeForCallAssignment;
    let name = ''
    let keys = ['prefix', 'firstName', 'middleName', 'lastName', 'suffix']
    keys.forEach((key) => {
      if (interviewee[key])
        name = name + ' ' + interviewee[key]
    })
    name = name.trim();
    return name === '' ? 'Unknown name' : name
  }

  formatPhoneNumber(number) {
    return '(' + number.slice(0, 3) + ') ' + number.slice(3, 6) + '-' + number.slice(7)
  }

  renderIntervieweeInfo() {
    let interviewee = this.props.currentUser.intervieweeForCallAssignment;
    let name = this.intervieweeName();
    let number = this.formatPhoneNumber(interviewee.phone)

    let sideBar = (

        {name}

        {number}

    )

    let location = interviewee.address.city + ', ' + interviewee.address.state + ' ' + interviewee.address.zip

    let content = (

        Location: {location}

        Local Time: {moment().utcOffset(interviewee.address.localTime).format('h:mm a')}

    )

    return (
      <SideBarLayout
        content={content}
        sideBar={sideBar}
        sideBarStyle={{
          width: 300,
        }}
        contentViewStyle={{
          marginLeft: 50,
        }}
      />
    )
  }

  render() {
    if (this.props.currentUser.intervieweeForCallAssignment === null)
      return (

            All done!

            We have no one left for you to call right now, but try again tomorrow.

      )
    let submitHandler = (formValue) => {
      if (this.state.completed)
        this.refs.survey.refs.component.submit()
    }

    let survey = (

        <Survey
          ref='survey'
          survey={this.props.callAssignment.survey}
          initialValues={{'email' : this.props.currentUser.intervieweeForCallAssignment.email}}
          onSubmit={() => {
            window.location.reload()
          } } />

    )

    let notCompletedQuestions =

    if (!this.state.completed)
      notCompletedQuestions = (

          <Form.Field
            name='reasonNotCompleted'
            type='select'
            choices={this.notCompletedReasons}
            label='Why not?'
            />

          <Form.Field
            name='leftVoicemail'
            label='Did you leave a voicemail?'
          />

      )
    submitHandler = submitHandler.bind(this)
    return (

        <Paper
          style={this.styles.assignmentBar}
        >
          {this.renderIntervieweeInfo()}
        </Paper>

          <GCForm
            schema={this.formSchema}
            onSubmit={submitHandler}
            value={this.state}
            onChange={(formValue) => {
              this.setState(formValue)
            }}
          >

              <Form.Field
                name='completed'
                label='Were you able to complete the call?'
              />
              {notCompletedQuestions}

            {survey}

              <Form.Button type='submit' label='Submit and on to the person!' fullWidth={true} style={this.styles.submitButton}/>

          </GCForm>

    );
  }
}

export default Relay.createContainer(CallAssignment, {
  initialVariables: { id: null },
  fragments: {
    callAssignment: () => Relay.QL`
      fragment on CallAssignment {
        id
        name
        survey {
          ${Survey.getFragment('survey')}
        }
      }
    `,
    currentUser: () => Relay.QL`
      fragment on User {
        id
        intervieweeForCallAssignment(callAssignmentId:$id) {
          prefix
          firstName
          middleName
          lastName
          suffix
          gender
          birthDate
          title
          employer
          occupation
          phone
          email
          address {
            city
            state
            zip
            localTime
          }
        }
      }
    `
  }
});<|endoftext|>