<commit_before>function TextRenderer(options) {
    if(!(this instanceof TextRenderer)) {
        return new TextRenderer(options);
    }
    this.options = options || {};
}

TextRenderer.prototype.code = function(code, lang, escaped) {
    return '\n\n' + code + '\n\n';
};

TextRenderer.prototype.blockquote = function(quote) {
    return '\t' + quote + '\n';
};

TextRenderer.prototype.html = function(html) {
    return html;
};

TextRenderer.prototype.heading = function(text, level, raw) {
    return text;
};

TextRenderer.prototype.hr = function() {
    return '\n\n';
};

TextRenderer.prototype.list = function(body, ordered) {
    return body;
};

TextRenderer.prototype.listitem = function(text) {
    return '\t' + text + '\n';
};

TextRenderer.prototype.paragraph = function(text) {
    return '\n' + text + '\n';
};

TextRenderer.prototype.table = function(header, body) {
    return '\n' + header + '\n' + body + '\n\n';
};

TextRenderer.prototype.tablerow = function(content) {
    return content + '\n';
};

TextRenderer.prototype.tablecell = function(content, flags) {
    return content + '\t';
};

// span level renderer
TextRenderer.prototype.strong = function(text) {
    return text;
};

TextRenderer.prototype.em = function(text) {
    return text;
};

TextRenderer.prototype.codespan = function(text) {
    return text;
};

TextRenderer.prototype.br = function() {
    return '\n\n';
};

TextRenderer.prototype.del = function(text) {
    return text;
};

TextRenderer.prototype.link = function(href, title, text) {
    return [title, text].filter(Boolean).join(' ');
};

TextRenderer.prototype.image = function(href, title, text) {
    return [title, text].filter(Boolean).join(' ');
};

// Exports
module.exports = TextRenderer;

<commit_msg>Add math method as NOOP<commit_after>function TextRenderer(options) {
    if(!(this instanceof TextRenderer)) {
        return new TextRenderer(options);
    }
    this.options = options || {};
}

TextRenderer.prototype.code = function(code, lang, escaped) {
    return '\n\n' + code + '\n\n';
};

TextRenderer.prototype.blockquote = function(quote) {
    return '\t' + quote + '\n';
};

TextRenderer.prototype.html = function(html) {
    return html;
};

TextRenderer.prototype.heading = function(text, level, raw) {
    return text;
};

TextRenderer.prototype.hr = function() {
    return '\n\n';
};

TextRenderer.prototype.list = function(body, ordered) {
    return body;
};

TextRenderer.prototype.listitem = function(text) {
    return '\t' + text + '\n';
};

TextRenderer.prototype.paragraph = function(text) {
    return '\n' + text + '\n';
};

TextRenderer.prototype.table = function(header, body) {
    return '\n' + header + '\n' + body + '\n\n';
};

TextRenderer.prototype.tablerow = function(content) {
    return content + '\n';
};

TextRenderer.prototype.tablecell = function(content, flags) {
    return content + '\t';
};

// span level renderer
TextRenderer.prototype.strong = function(text) {
    return text;
};

TextRenderer.prototype.em = function(text) {
    return text;
};

TextRenderer.prototype.codespan = function(text) {
    return text;
};

TextRenderer.prototype.br = function() {
    return '\n\n';
};

TextRenderer.prototype.del = function(text) {
    return text;
};

TextRenderer.prototype.link = function(href, title, text) {
    return [title, text].filter(Boolean).join(' ');
};

TextRenderer.prototype.image = function(href, title, text) {
    return [title, text].filter(Boolean).join(' ');
};

TextRenderer.prototype.math = noop;

function noop() {
    return '\n';
}

// Exports
module.exports = TextRenderer;

<|endoftext|>


<commit_before>require('dotenv').config();

process.on('unhandledRejection', (error) => {
	console.error(error);
});

const {RtmClient, WebClient, CLIENT_EVENTS, RTM_EVENTS} = require('@slack/client');
const shuffle = require('shuffle-array');
const {stripIndent} = require('common-tags');
const fs = require('fs');
const {promisify} = require('util');
const {chunk} = require('lodash');

const calculator = require('./calculator.js');
const savedState = (() => {
	try {
		// eslint-disable-next-line global-require
		return require('./current-point.json');
	} catch (e) {
		return {
			points: 25000,
			wins: 0,
			loses: 0,
		};
	}
})();

const rtm = new RtmClient(process.env.SLACK_TOKEN);
const slack = new WebClient(process.env.SLACK_TOKEN);

const get牌Type = (牌) => {
	const codePoint = 牌.codePointAt(0);

	if (0x1F000 <= codePoint && codePoint <= 0x1F006) {
		return '字牌';
	}

	if (0x1F007 <= codePoint && codePoint <= 0x1F00F) {
		return '萬子';
	}

	if (0x1F010 <= codePoint && codePoint <= 0x1F018) {
		return '索子';
	}

	if (0x1F019 <= codePoint && codePoint <= 0x1F021) {
		return '筒子';
	}

	return null;
};

const 牌Orders = ['萬子', '筒子', '索子', '字牌'];

const 漢数字s = ['一', '二', '三', '四', '五', '六', '七', '八', '九'];

const 牌Names = [
	'東', '南', '西', '北', '中', '發', '白',
	...(漢数字s.map((漢数字) => `${漢数字}萬`)),
	...(漢数字s.map((漢数字) => `${漢数字}索`)),
	...(漢数字s.map((漢数字) => `${漢数字}筒`)),
	'赤五萬', '赤五索', '赤五筒',
];

const nameTo牌 = (name) => {
	const normalized = name.startsWith('赤') ? name.slice(1) : name;
	const 牌 = String.fromCodePoint(0x1F000 + 牌Names.indexOf(normalized));
	if (name.startsWith('赤')) {
		return `${牌}\uFE00`;
	}
	return 牌;
};

const 牌ToName = (牌) => {
	const normalized牌 = 牌.replace(/\uFE00$/, '');
	const name = 牌Names[normalized牌.codePointAt(0) - 0x1F000];
	if (牌.endsWith('\uFE00')) {
		return `赤${name}`;
	}
	return name;
};

const sort = (牌s) => (
	牌s.sort((牌A, 牌B) => {
		const 牌AIndex = 牌Orders.indexOf(get牌Type(牌A));
		const 牌BIndex = 牌Orders.indexOf(get牌Type(牌B));

		if (牌AIndex !== 牌BIndex) {
			return 牌AIndex - 牌BIndex;
		}

		if (牌A.codePointAt(0) !== 牌B.codePointAt(0)) {
			return 牌A.codePointAt(0) - 牌B.codePointAt(0);
		}

		return Array.from(牌B).length - Array.from(牌A).length;
	})
);

const state = {
	phase: 'waiting',
	手牌: [],
	壁牌: [],
	remaining自摸: 0,
	points: savedState.points,
	リーチTurn: null,
	wins: savedState.wins,
	loses: savedState.loses,
};

const 麻雀牌 = Array(136).fill(0).map((_, index) => {
	const 牌 = String.fromCodePoint(0x1F000 + Math.floor(index / 4));
	const 同牌Index = index % 4;

	if (
		(牌 === '🀋' && 同牌Index === 0) ||
		(牌 === '🀔' && 同牌Index === 0) ||
		(牌 === '🀝' && (同牌Index === 0 || 同牌Index === 1))
	) {
		return `${牌}\uFE00`;
	}

	return 牌;
});

const saveState = async () => {
	await promisify(fs.writeFile)('current-point.json', JSON.stringify({
		points: state.points,
		wins: state.wins,
		loses: state.loses,
	}));
};

rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (data) => {
	console.log(`Logged in as ${data.self.name} of team ${data.team.name}`);
});

rtm.on(RTM_EVENTS.MESSAGE, async (message) => {
	const postMessage = (text, 手牌 = null) => {
		slack.chat.postMessage(message.channel, text, {
			username: 'mahjong',
			// eslint-disable-next-line camelcase
			icon_emoji: ':mahjong:',
			...(手牌 === null ? {} : {
				attachments: [{
					// eslint-disable-next-line camelcase
					image_url: `https://mahjong.hakatashi.com/images/${encodeURIComponent(手牌.join(''))}`,
					fallback: 手牌.join(''),
				}],
			}),
		});
	};

	const perdon = () => {
		postMessage(':ha:');
	};

	const checkPoints = async () => {
		if (state.points < 0) {
			state.loses++;
			state.points = 25000;
			await saveState();
			postMessage(stripIndent`
				ハコ割れしました。点数をリセットします。
				通算成績: ${state.wins}勝${state.loses}敗
			`);
		} else if (state.points > 50000) {
			state.wins++;
			state.points = 25000;
			await saveState();
			postMessage(stripIndent`
				勝利しました。点数をリセットします。
				通算成績: ${state.wins}勝${state.loses}敗
			`);
		}
	};

	if (message.channel !== process.env.CHANNEL) {
		return;
	}

	if (message.subtype === 'bot_message') {
		return;
	}

	if (!message.text) {
		return;
	}

	const text = message.text.trim();

	if (['カン', 'ポン', 'チー', 'ロン'].includes(text)) {
		perdon();
		return;
	}

	if (text === '配牌') {
		if (state.phase !== 'waiting') {
			perdon();
			return;
		}

		state.phase = 'gaming';
		const shuffled牌s = shuffle(麻雀牌);
		state.手牌 = sort(shuffled牌s.slice(0, 14));
		state.壁牌 = shuffled牌s.slice(14);
		state.remaining自摸 = 17;
		state.points -= 1500;
		await saveState();

		postMessage(stripIndent`
			場代 -3000点
			現在の得点: ${state.points}点

			残り${state.remaining自摸}牌
		`, state.手牌);
		return;
	}

	if (text === '残り牌') {
		if (state.phase !== 'gaming') {
			perdon();
			return;
		}

		const 残り牌List = new Array(34).fill(0);
		state.壁牌.forEach((牌) => {
			残り牌List[牌.codePointAt(0) - 0x1F000]++;
		});
		postMessage(stripIndent`
			萬子: ${chunk(残り牌List.slice(7, 16), 3).map((numbers) => numbers.join('')).join(' ')}
			筒子: ${chunk(残り牌List.slice(25, 34), 3).map((numbers) => numbers.join('')).join(' ')}
			索子: ${chunk(残り牌List.slice(16, 25), 3).map((numbers) => numbers.join('')).join(' ')}
			${牌Names.slice(0, 7).map((name, index) => `${name}${残り牌List[index]}`).join(' ')}
		`);
		return;
	}

	if (text.startsWith('打') || text === 'ツモ切り') {
		if (state.phase !== 'gaming') {
			perdon();
			return;
		}

		if (text === 'ツモ切り') {
			state.手牌 = state.手牌.slice(0, -1);
		} else {
			const 牌Name = text.slice(1);
			if (!牌Names.includes(牌Name)) {
				perdon();
				return;
			}

			const 打牌 = nameTo牌(牌Name);

			if (!state.手牌.includes(打牌)) {
				perdon();
				return;
			}

			state.手牌.splice(state.手牌.indexOf(打牌), 1);
		}

		if (state.remaining自摸 === 0) {
			state.phase = 'waiting';
			const isTenpai = calculator.tenpai(state.手牌);
			if (isTenpai) {
				postMessage(stripIndent`
					聴牌 0点
					現在の得点: ${state.points}点
				`);
			} else {
				state.points -= 3000;
				await saveState();
				postMessage(stripIndent`
					不聴罰符 -3000点
					現在の得点: ${state.points}点
				`);
			}

			await checkPoints();
			return;
		}

		state.手牌 = sort(state.手牌).concat([state.壁牌[0]]);
		state.壁牌 = state.壁牌.slice(1);
		state.remaining自摸--;

		postMessage(stripIndent`
			摸${牌ToName(state.手牌[state.手牌.length - 1])} 残り${state.remaining自摸}牌
		`, state.手牌);
	}

	if (text.startsWith('リーチ ')) {
		if (state.phase !== 'gaming') {
			perdon();
			return;
		}

		const instruction = text.slice('リーチ '.length);

		if (!instruction.startsWith('打') && instruction !== 'ツモ切り') {
			perdon();
			return;
		}

		if (instruction === 'ツモ切り') {
			state.手牌 = state.手牌.slice(0, -1);
		} else {
			const 牌Name = instruction.slice(1);
			if (!牌Names.includes(牌Name)) {
				perdon();
				return;
			}

			const 打牌 = nameTo牌(牌Name);

			if (!state.手牌.includes(打牌)) {
				perdon();
				return;
			}

			state.手牌.splice(state.手牌.indexOf(打牌), 1);
		}

		state.手牌 = sort(state.手牌);
		state.phase = 'リーチ';
		state.リーチTurn = state.remaining自摸;

		// TODO: フリテン
		while (state.remaining自摸 > 0) {
			state.remaining自摸--;

			const 河牌s = state.壁牌.slice(0, 4);
			state.壁牌 = state.壁牌.slice(4);

			const 当たり牌Index = 河牌s.findIndex((牌) => {
				const {agari} = calculator.agari(state.手牌.concat([牌]), {isRiichi: false});
				return agari.isAgari;
			});

			if (当たり牌Index !== -1) {
				const {agari, 役s} = calculator.agari(state.手牌.concat([河牌s[当たり牌Index]]), {
					isHaitei: state.remaining自摸 === 0 && 当たり牌Index === 3,
					isVirgin: false,
					isRiichi: true,
					isDoubleRiichi: state.リーチTurn === 17,
					isIppatsu: state.リーチTurn - state.remaining自摸 === 1,
					isRon: 当たり牌Index !== 3,
				});

				state.points += agari.delta[0];
				await saveState();
				postMessage(stripIndent`
					河${河牌s.slice(0, Math.min(当たり牌Index + 1, 3)).map(牌ToName).join('・')}${当たり牌Index === 3 ? ` 摸${牌ToName(河牌s[河牌s.length - 1])}` : ''}
					${当たり牌Index === 3 ? 'ツモ!!!' : 'ロン!!!'}

					${役s.join('・')}
					${agari.delta[0]}点
					現在の得点: ${state.points}点
				`, state.手牌.concat([河牌s[当たり牌Index]]));
				await checkPoints();
				state.phase = 'waiting';
				return;
			}

			postMessage(stripIndent`
				河${河牌s.slice(0, 3).map(牌ToName).join('・')} 摸${牌ToName(河牌s[河牌s.length - 1])} 残り${state.remaining自摸}牌
			`, state.手牌.concat([河牌s[3]]));

			await new Promise((resolve) => {
				setTimeout(resolve, 3000);
			});
		}

		state.phase = 'waiting';
		const isTenpai = calculator.tenpai(state.手牌);
		if (isTenpai) {
			state.points -= 1000;
			await saveState();
			postMessage(stripIndent`
				流局 供託点 -1000点
				現在の得点: ${state.points}点
			`);
		} else {
			state.points -= 12000;
			await saveState();
			postMessage(stripIndent`
				流局 不聴立直 -12000点
				現在の得点: ${state.points}点
			`);
		}

		await checkPoints();
		return;
	}

	if (text === 'ツモ') {
		if (state.phase !== 'gaming') {
			perdon();
			return;
		}

		const {agari, 役s} = calculator.agari(state.手牌, {
			isHaitei: state.remaining自摸 === 0,
			isVirgin: state.remaining自摸 === 17,
		});

		state.phase = 'waiting';

		if (!agari.isAgari) {
			state.points -= 12000;
			await saveState();
			postMessage(stripIndent`
				錯和 -12000点
				現在の得点: ${state.points}点
			`);
			await checkPoints();
			return;
		}

		state.points += agari.delta[0];
		await saveState();
		postMessage(stripIndent`
			ツモ!!!
			${役s.join('・')}
			${agari.delta[0]}点
			現在の得点: ${state.points}点
		`);
		await checkPoints();
	}
});

rtm.start();
<commit_msg>Fix 場代 text<commit_after>require('dotenv').config();

process.on('unhandledRejection', (error) => {
	console.error(error);
});

const {RtmClient, WebClient, CLIENT_EVENTS, RTM_EVENTS} = require('@slack/client');
const shuffle = require('shuffle-array');
const {stripIndent} = require('common-tags');
const fs = require('fs');
const {promisify} = require('util');
const {chunk} = require('lodash');

const calculator = require('./calculator.js');
const savedState = (() => {
	try {
		// eslint-disable-next-line global-require
		return require('./current-point.json');
	} catch (e) {
		return {
			points: 25000,
			wins: 0,
			loses: 0,
		};
	}
})();

const rtm = new RtmClient(process.env.SLACK_TOKEN);
const slack = new WebClient(process.env.SLACK_TOKEN);

const get牌Type = (牌) => {
	const codePoint = 牌.codePointAt(0);

	if (0x1F000 <= codePoint && codePoint <= 0x1F006) {
		return '字牌';
	}

	if (0x1F007 <= codePoint && codePoint <= 0x1F00F) {
		return '萬子';
	}

	if (0x1F010 <= codePoint && codePoint <= 0x1F018) {
		return '索子';
	}

	if (0x1F019 <= codePoint && codePoint <= 0x1F021) {
		return '筒子';
	}

	return null;
};

const 牌Orders = ['萬子', '筒子', '索子', '字牌'];

const 漢数字s = ['一', '二', '三', '四', '五', '六', '七', '八', '九'];

const 牌Names = [
	'東', '南', '西', '北', '中', '發', '白',
	...(漢数字s.map((漢数字) => `${漢数字}萬`)),
	...(漢数字s.map((漢数字) => `${漢数字}索`)),
	...(漢数字s.map((漢数字) => `${漢数字}筒`)),
	'赤五萬', '赤五索', '赤五筒',
];

const nameTo牌 = (name) => {
	const normalized = name.startsWith('赤') ? name.slice(1) : name;
	const 牌 = String.fromCodePoint(0x1F000 + 牌Names.indexOf(normalized));
	if (name.startsWith('赤')) {
		return `${牌}\uFE00`;
	}
	return 牌;
};

const 牌ToName = (牌) => {
	const normalized牌 = 牌.replace(/\uFE00$/, '');
	const name = 牌Names[normalized牌.codePointAt(0) - 0x1F000];
	if (牌.endsWith('\uFE00')) {
		return `赤${name}`;
	}
	return name;
};

const sort = (牌s) => (
	牌s.sort((牌A, 牌B) => {
		const 牌AIndex = 牌Orders.indexOf(get牌Type(牌A));
		const 牌BIndex = 牌Orders.indexOf(get牌Type(牌B));

		if (牌AIndex !== 牌BIndex) {
			return 牌AIndex - 牌BIndex;
		}

		if (牌A.codePointAt(0) !== 牌B.codePointAt(0)) {
			return 牌A.codePointAt(0) - 牌B.codePointAt(0);
		}

		return Array.from(牌B).length - Array.from(牌A).length;
	})
);

const state = {
	phase: 'waiting',
	手牌: [],
	壁牌: [],
	remaining自摸: 0,
	points: savedState.points,
	リーチTurn: null,
	wins: savedState.wins,
	loses: savedState.loses,
};

const 麻雀牌 = Array(136).fill(0).map((_, index) => {
	const 牌 = String.fromCodePoint(0x1F000 + Math.floor(index / 4));
	const 同牌Index = index % 4;

	if (
		(牌 === '🀋' && 同牌Index === 0) ||
		(牌 === '🀔' && 同牌Index === 0) ||
		(牌 === '🀝' && (同牌Index === 0 || 同牌Index === 1))
	) {
		return `${牌}\uFE00`;
	}

	return 牌;
});

const saveState = async () => {
	await promisify(fs.writeFile)('current-point.json', JSON.stringify({
		points: state.points,
		wins: state.wins,
		loses: state.loses,
	}));
};

rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (data) => {
	console.log(`Logged in as ${data.self.name} of team ${data.team.name}`);
});

rtm.on(RTM_EVENTS.MESSAGE, async (message) => {
	const postMessage = (text, 手牌 = null) => {
		slack.chat.postMessage(message.channel, text, {
			username: 'mahjong',
			// eslint-disable-next-line camelcase
			icon_emoji: ':mahjong:',
			...(手牌 === null ? {} : {
				attachments: [{
					// eslint-disable-next-line camelcase
					image_url: `https://mahjong.hakatashi.com/images/${encodeURIComponent(手牌.join(''))}`,
					fallback: 手牌.join(''),
				}],
			}),
		});
	};

	const perdon = () => {
		postMessage(':ha:');
	};

	const checkPoints = async () => {
		if (state.points < 0) {
			state.loses++;
			state.points = 25000;
			await saveState();
			postMessage(stripIndent`
				ハコ割れしました。点数をリセットします。
				通算成績: ${state.wins}勝${state.loses}敗
			`);
		} else if (state.points > 50000) {
			state.wins++;
			state.points = 25000;
			await saveState();
			postMessage(stripIndent`
				勝利しました。点数をリセットします。
				通算成績: ${state.wins}勝${state.loses}敗
			`);
		}
	};

	if (message.channel !== process.env.CHANNEL) {
		return;
	}

	if (message.subtype === 'bot_message') {
		return;
	}

	if (!message.text) {
		return;
	}

	const text = message.text.trim();

	if (['カン', 'ポン', 'チー', 'ロン'].includes(text)) {
		perdon();
		return;
	}

	if (text === '配牌') {
		if (state.phase !== 'waiting') {
			perdon();
			return;
		}

		state.phase = 'gaming';
		const shuffled牌s = shuffle(麻雀牌);
		state.手牌 = sort(shuffled牌s.slice(0, 14));
		state.壁牌 = shuffled牌s.slice(14);
		state.remaining自摸 = 17;
		state.points -= 1500;
		await saveState();

		postMessage(stripIndent`
			場代 -1500点
			現在の得点: ${state.points}点

			残り${state.remaining自摸}牌
		`, state.手牌);
		return;
	}

	if (text === '残り牌') {
		if (state.phase !== 'gaming') {
			perdon();
			return;
		}

		const 残り牌List = new Array(34).fill(0);
		state.壁牌.forEach((牌) => {
			残り牌List[牌.codePointAt(0) - 0x1F000]++;
		});
		postMessage(stripIndent`
			萬子: ${chunk(残り牌List.slice(7, 16), 3).map((numbers) => numbers.join('')).join(' ')}
			筒子: ${chunk(残り牌List.slice(25, 34), 3).map((numbers) => numbers.join('')).join(' ')}
			索子: ${chunk(残り牌List.slice(16, 25), 3).map((numbers) => numbers.join('')).join(' ')}
			${牌Names.slice(0, 7).map((name, index) => `${name}${残り牌List[index]}`).join(' ')}
		`);
		return;
	}

	if (text.startsWith('打') || text === 'ツモ切り') {
		if (state.phase !== 'gaming') {
			perdon();
			return;
		}

		if (text === 'ツモ切り') {
			state.手牌 = state.手牌.slice(0, -1);
		} else {
			const 牌Name = text.slice(1);
			if (!牌Names.includes(牌Name)) {
				perdon();
				return;
			}

			const 打牌 = nameTo牌(牌Name);

			if (!state.手牌.includes(打牌)) {
				perdon();
				return;
			}

			state.手牌.splice(state.手牌.indexOf(打牌), 1);
		}

		if (state.remaining自摸 === 0) {
			state.phase = 'waiting';
			const isTenpai = calculator.tenpai(state.手牌);
			if (isTenpai) {
				postMessage(stripIndent`
					聴牌 0点
					現在の得点: ${state.points}点
				`);
			} else {
				state.points -= 3000;
				await saveState();
				postMessage(stripIndent`
					不聴罰符 -3000点
					現在の得点: ${state.points}点
				`);
			}

			await checkPoints();
			return;
		}

		state.手牌 = sort(state.手牌).concat([state.壁牌[0]]);
		state.壁牌 = state.壁牌.slice(1);
		state.remaining自摸--;

		postMessage(stripIndent`
			摸${牌ToName(state.手牌[state.手牌.length - 1])} 残り${state.remaining自摸}牌
		`, state.手牌);
	}

	if (text.startsWith('リーチ ')) {
		if (state.phase !== 'gaming') {
			perdon();
			return;
		}

		const instruction = text.slice('リーチ '.length);

		if (!instruction.startsWith('打') && instruction !== 'ツモ切り') {
			perdon();
			return;
		}

		if (instruction === 'ツモ切り') {
			state.手牌 = state.手牌.slice(0, -1);
		} else {
			const 牌Name = instruction.slice(1);
			if (!牌Names.includes(牌Name)) {
				perdon();
				return;
			}

			const 打牌 = nameTo牌(牌Name);

			if (!state.手牌.includes(打牌)) {
				perdon();
				return;
			}

			state.手牌.splice(state.手牌.indexOf(打牌), 1);
		}

		state.手牌 = sort(state.手牌);
		state.phase = 'リーチ';
		state.リーチTurn = state.remaining自摸;

		// TODO: フリテン
		while (state.remaining自摸 > 0) {
			state.remaining自摸--;

			const 河牌s = state.壁牌.slice(0, 4);
			state.壁牌 = state.壁牌.slice(4);

			const 当たり牌Index = 河牌s.findIndex((牌) => {
				const {agari} = calculator.agari(state.手牌.concat([牌]), {isRiichi: false});
				return agari.isAgari;
			});

			if (当たり牌Index !== -1) {
				const {agari, 役s} = calculator.agari(state.手牌.concat([河牌s[当たり牌Index]]), {
					isHaitei: state.remaining自摸 === 0 && 当たり牌Index === 3,
					isVirgin: false,
					isRiichi: true,
					isDoubleRiichi: state.リーチTurn === 17,
					isIppatsu: state.リーチTurn - state.remaining自摸 === 1,
					isRon: 当たり牌Index !== 3,
				});

				state.points += agari.delta[0];
				await saveState();
				postMessage(stripIndent`
					河${河牌s.slice(0, Math.min(当たり牌Index + 1, 3)).map(牌ToName).join('・')}${当たり牌Index === 3 ? ` 摸${牌ToName(河牌s[河牌s.length - 1])}` : ''}
					${当たり牌Index === 3 ? 'ツモ!!!' : 'ロン!!!'}

					${役s.join('・')}
					${agari.delta[0]}点
					現在の得点: ${state.points}点
				`, state.手牌.concat([河牌s[当たり牌Index]]));
				await checkPoints();
				state.phase = 'waiting';
				return;
			}

			postMessage(stripIndent`
				河${河牌s.slice(0, 3).map(牌ToName).join('・')} 摸${牌ToName(河牌s[河牌s.length - 1])} 残り${state.remaining自摸}牌
			`, state.手牌.concat([河牌s[3]]));

			await new Promise((resolve) => {
				setTimeout(resolve, 3000);
			});
		}

		state.phase = 'waiting';
		const isTenpai = calculator.tenpai(state.手牌);
		if (isTenpai) {
			state.points -= 1000;
			await saveState();
			postMessage(stripIndent`
				流局 供託点 -1000点
				現在の得点: ${state.points}点
			`);
		} else {
			state.points -= 12000;
			await saveState();
			postMessage(stripIndent`
				流局 不聴立直 -12000点
				現在の得点: ${state.points}点
			`);
		}

		await checkPoints();
		return;
	}

	if (text === 'ツモ') {
		if (state.phase !== 'gaming') {
			perdon();
			return;
		}

		const {agari, 役s} = calculator.agari(state.手牌, {
			isHaitei: state.remaining自摸 === 0,
			isVirgin: state.remaining自摸 === 17,
		});

		state.phase = 'waiting';

		if (!agari.isAgari) {
			state.points -= 12000;
			await saveState();
			postMessage(stripIndent`
				錯和 -12000点
				現在の得点: ${state.points}点
			`);
			await checkPoints();
			return;
		}

		state.points += agari.delta[0];
		await saveState();
		postMessage(stripIndent`
			ツモ!!!
			${役s.join('・')}
			${agari.delta[0]}点
			現在の得点: ${state.points}点
		`);
		await checkPoints();
	}
});

rtm.start();
<|endoftext|>


<commit_before>/*
Running documentation of ObjectSchema

possible settings per field:
----------------------------
ignored: the field and value will be silently dropped
required: must not be undefined - specifiy more settings to expand on what it
            should be to pass
type: the type of object - i.e. the text string from typeof [object]
instanceOf: check that the value is an instance of the object in instanceOf
filters: filters to run on the value, before validating. Can be:
           text string - will check if it's a native string function or if
                         ObjectSchema has such a filter built in
           function - any custome function, that takes a value, manipulates it
                      and returns the result.
in: the value should exist in the given array.
default: a default value to set, if the field has no value.
regex: a regular expression to match
*/

var ObjectId = require('mongodb').ObjectID;
var DBRef = require('mongodb').DBRef;
require('extend-string');
var clone = require('clone');

/**
 * @todo Validate the schema - e.g. required and ignored not set on the same field
 */
var ObjectSchema = function(schemaDefinition, options) {
  if (options && options.strictness) {
    this.strictness = options.strictness;
  }
  this.definition = schemaDefinition;
};
module.exports = ObjectSchema;

/**
 * @todo A schema settings validator is needed here...
 */
ObjectSchema.prototype.setField = function(field, settings) {
  this.definition[field] = settings;
};
ObjectSchema.prototype.setFields = function(fields) {
  for (var field in fields) {
    this.definition[field] = fields[field];
  }
};
ObjectSchema.prototype.mergeFields = function(fields) {
  for (var field in fields) {
    this.addSettings(field, fields[field]);
  }
  this.definition = this._mergeObjects(this.definition, fields);
};
ObjectSchema.prototype.addSettings = function(field, settings) {
  if (!this.definition[field]) {
    this.definition[field] = settings;
  } else {
    this.definition[field] = this._mergeObjects(this.definition[field], settings);
  }
};

/**
 * Schema strictness
 * Possible values are:
 * strict: err on fields not defined in the schema
 * relaxed: ignore fields not defined in the schema
 * loose: let fields through, when they're not defined in the schema
 */
ObjectSchema.prototype.setStrictness = function(strictness) {
  this.strictness = strictness || 'strict'; // strict | relaxed | loose
  if (['strict', 'relaxed', 'loose'].indexOf(this.strictness) === -1) {
    throw new Error('You need to supply a valid strictness or leave as strict');
  }
};

/**
 * Not all validators are validators - some are manipulators...
 * Those should be run first, as they can change a value.
 * We don't want to pass a value, then change it to a non-passable value.
 */
ObjectSchema.prototype.validate = function(dataObject, errors, callback) {
  this.lastErrors = [];

  if (arguments.length < 3) {
    if (typeof errors === 'function') {
      callback = errors;
      errors = [];
    }
  }
  errors = errors || [];

  if (!dataObject) {
    callback([new Error('No dataObject supplied')], null);
    return false;
  }

  var testObject = clone(dataObject);

  if (testObject instanceof Array) var result = [];
  else var result = {};

  for (var field in this.definition) {
    var definition = this.definition[field];

    if (field === '*') {
      for (var dataField in testObject) {
        var res = this.runValidations(dataField, definition, testObject, errors);
        if (res !== undefined) {
          result[dataField] = res
        }
      }

    } else {
      var res = this.runValidations(field, definition, testObject, errors);
      if (res !== undefined) {
        result[field] = res
      }
    }
  }
  ;

  /** Run through the fields that are not in the schema */
  for (var field in testObject) {
    if (!this.definition[field] && !this.definition['*']) {
      if (this.strictness === 'strict') {
        errors.push({
          field: field,
          error: 'field not in definition'
        });
      } else if (this.strictness === 'loose') {
        result[field] = testObject[field];
      }
    }
  }

  if (errors.length) {
    this.lastErrors = errors;
  }

  /** If there is anything defined as an error - don't return the result! */
  if (this.strictness === 'strict' && errors.length) {
    result = false;
  }
  if (!errors.length) {
    errors = null;
  }

  if (callback) {
    callback(errors, result);
  }
  return result;
};

/** This is a seperated form validate, in order to only having it once */
ObjectSchema.prototype.runValidations = function(field, definition, testObject, errors) {
  var passed = true;

  if (definition.ignored) {
    return;
  }

  if ((this.strictness !== 'strict' || definition.optional) &&
    !definition.required &&
    (!testObject || typeof testObject[field] === 'undefined')) {

    if (definition.default) return definition.default;
    else return;
  }

  if (definition.objectSchema && typeof definition.objectSchema === 'object') {
    if (!(definition.objectSchema instanceof ObjectSchema)) {
      definition.objectSchema = new ObjectSchema(definition.objectSchema);
    }
    var subErrors = [];
    var result = definition.objectSchema.validate(testObject[field], subErrors);
    if (!result) {
      passed = false;
    } else {
      testObject[field] = result;
    }
    if (subErrors.length) {
      for (var i in subErrors) {
        subErrors[i].subField = subErrors[i].field;
        subErrors[i].field = field;
        errors.push(subErrors[i]);
      }
    }
  }

  /** Run theses before the tests, as these can render the value invalid */
  if (definition.filters) {
    this.definitionFilters(field, definition, testObject, errors);
  }
  if (definition.default) {
    this.definitionDefault(field, definition, testObject, errors);
  }

  if (definition.required) {
    if (!this.definitionRequired(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (definition.in) {
    if (!this.definitionIn(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (definition.type) {
    if (!this.definitionType(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (definition.instanceOf && !definition.objectSchema) {
    if (!this.definitionInstanceOf(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (definition.regex) {
    if (!this.definitionRegEx(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (passed) {
    return testObject[field];
  }
};

/*************************\
 * DEFINITION VALIDATORS *
\*************************/
ObjectSchema.prototype.definitionFilters = function(field, definition, testObject, errors) {
  if (definition.filters) {
    if (definition.filters && typeof definition.filters !== 'object') {
      definition.filters = [definition.filters];
    }
    if (typeof definition.filters === 'object') {
      for (var i in definition.filters) {
        if (typeof definition.filters[i] === 'string') {
          var filterName = definition.filters[i];

          if (testObject[field][filterName] &&
            typeof testObject[field][filterName] === 'function') {
            testObject[field] = testObject[field][filterName]();

          } else if (this['filter' + filterName.upperCaseFirst()] &&
            typeof this['filter' + filterName.upperCaseFirst()] === 'function') {
            testObject[field] = this['filter' + filterName.upperCaseFirst()](field, testObject);
          }

        } else if (typeof definition.filters[i] === 'function') {
          testObject[field] = definition.filters[i](field, testObject);
        }
      }
    }
  }
};

/**
 * What is an empty value?
 * What it isn't: an empty value of the correct type...
 */
ObjectSchema.prototype.definitionDefault = function(field, definition, testObject, errors) {
  /** If no value is found, even after running filters, set the default */
  if (definition.default && typeof testObject[field] === 'undefined') {
    testObject[field] = definition.default;
  }
};

ObjectSchema.prototype.definitionRequired = function(field, definition, testObject, errors) {
  if (typeof testObject[field] === 'undefined') {
    errors.push({
      field: field,
      error: 'does not exist'
    });
    return false;
  }
  return true;
};

ObjectSchema.prototype.definitionIn = function(field, definition, testObject, errors) {
  if (typeof definition.in === 'object' && definition.in.length) {
    if (definition.in.indexOf(testObject[field]) === -1) {
      errors.push({
        field: field,
        error: 'value wasn\'t in array'
      });
      return false;
    }
  } else {
    return false;
  }
  return true;
};

ObjectSchema.prototype.definitionType = function(field, definition, testObject, errors) {
  if (typeof testObject[field] !== definition.type) {
    errors.push({
      field: field,
      error: 'wrong type'
    });
    return false;
  }
  return true;
};

ObjectSchema.prototype.definitionInstanceOf = function(field, definition, testObject, errors) {
  if (!(testObject[field] instanceof definition.instanceOf)) {
    errors.push({
      field: field,
      error: 'incorrect instance type'
    });
    return false;
  }
  return true;
};

ObjectSchema.prototype.definitionRegEx = function(field, definition, testObject, errors) {
  if (!testObject[field].match(definition.regex)) {
    errors.push({
      field: field,
      value: testObject[field],
      regex: definition.regex,
      error: 'value does not match regex'
    });
    return false;
  }
  return true;
};

/***********\
 * FILTERS *
\***********/
ObjectSchema.prototype.filterDate = function(field, dataObject) {
  if (dataObject[field]) {
    var date = new Date(dataObject[field]);
    if (date.toString() !== 'Invalid Date') {
      return date;
    }
  }
};

ObjectSchema.prototype.filterObjectID = ObjectSchema.prototype.filterObjectId = function(field, dataObject) {
  var objectId = dataObject[field];

  if (objectId instanceof ObjectId) {
    return objectId;
  }

  if (objectId.constructor.name.match(/^ObjectI(d|D)$/)) {
    return objectId;
  }

  if (ObjectId.isValid('' + objectId)) {
    return new ObjectId('' + objectId);
  }

  return objectId;
};

ObjectSchema.prototype.filterDbref = ObjectSchema.prototype.filterDbRef = ObjectSchema.prototype.filterDBRef = function(field, dataObject) {
  var dbRef = dataObject[field];

  if (dbRef instanceof DBRef) {
    return dbRef;
  }

  if (dbRef.constructor.name === 'DBRef') {
    return dbRef;
  }

  try {
    var namespace = dbRef.namespace || dbRef.collection || dbRef.$ref;
    var oid = dbRef.oid || dbRef._id || dbRef.id || dbRef.$id;
    var db = dbRef.db || dbRef.$db || dbRef.database || '';
    oid = ObjectId('' + oid);

    dbRef = new DBRef(namespace, oid, db);
  } catch ( e ) {}

  return dbRef;
};

/*************\
 * UTILITIES *
\*************/
ObjectSchema.prototype._mergeObjects = function() {
  var newObject = {};
  for (var objectIndex in arguments) {
    for (var objectKey in arguments[objectIndex]) {
      newObject[objectKey] = arguments[objectIndex][objectKey];
    }
  }
  return newObject;
};

<commit_msg>Just to be sure it is gone<commit_after>/*
Running documentation of ObjectSchema

possible settings per field:
----------------------------
ignored: the field and value will be silently dropped
required: must not be undefined - specifiy more settings to expand on what it
            should be to pass
type: the type of object - i.e. the text string from typeof [object]
instanceOf: check that the value is an instance of the object in instanceOf
filters: filters to run on the value, before validating. Can be:
           text string - will check if it's a native string function or if
                         ObjectSchema has such a filter built in
           function - any custome function, that takes a value, manipulates it
                      and returns the result.
in: the value should exist in the given array.
default: a default value to set, if the field has no value.
regex: a regular expression to match
*/

var ObjectId = require('mongodb').ObjectID;
var DBRef = require('mongodb').DBRef;
require('extend-string');
var clone = require('clone');

/**
 * @todo Validate the schema - e.g. required and ignored not set on the same field
 */
var ObjectSchema = function(schemaDefinition, options) {
  if (options && options.strictness) {
    this.strictness = options.strictness;
  }
  this.definition = schemaDefinition;
};
module.exports = ObjectSchema;

/**
 * @todo A schema settings validator is needed here...
 */
ObjectSchema.prototype.setField = function(field, settings) {
  this.definition[field] = settings;
};
ObjectSchema.prototype.setFields = function(fields) {
  for (var field in fields) {
    this.definition[field] = fields[field];
  }
};
ObjectSchema.prototype.mergeFields = function(fields) {
  for (var field in fields) {
    this.addSettings(field, fields[field]);
  }
  this.definition = this._mergeObjects(this.definition, fields);
};
ObjectSchema.prototype.addSettings = function(field, settings) {
  if (!this.definition[field]) {
    this.definition[field] = settings;
  } else {
    this.definition[field] = this._mergeObjects(this.definition[field], settings);
  }
};

/**
 * Schema strictness
 * Possible values are:
 * strict: err on fields not defined in the schema
 * relaxed: ignore fields not defined in the schema
 * loose: let fields through, when they're not defined in the schema
 */
ObjectSchema.prototype.setStrictness = function(strictness) {
  this.strictness = strictness || 'strict'; // strict | relaxed | loose
  if (['strict', 'relaxed', 'loose'].indexOf(this.strictness) === -1) {
    throw new Error('You need to supply a valid strictness or leave as strict');
  }
};

/**
 * Not all validators are validators - some are manipulators...
 * Those should be run first, as they can change a value.
 * We don't want to pass a value, then change it to a non-passable value.
 */
ObjectSchema.prototype.validate = function(dataObject, errors, callback) {
  this.lastErrors = [];

  if (arguments.length < 3) {
    if (typeof errors === 'function') {
      callback = errors;
      errors = [];
    }
  }
  errors = errors || [];

  if (!dataObject) {
    callback([new Error('No dataObject supplied')], null);
    return false;
  }

  var testObject = clone(dataObject);

  if (testObject instanceof Array) var result = [];
  else var result = {};

  for (var field in this.definition) {
    var definition = this.definition[field];

    if (field === '*') {
      for (var dataField in testObject) {
        var res = this.runValidations(dataField, definition, testObject, errors);
        if (res !== undefined) {
          result[dataField] = res
        }
      }

    } else {
      var res = this.runValidations(field, definition, testObject, errors);
      if (res !== undefined) {
        result[field] = res
      }
    }
  }
  ;

  /** Run through the fields that are not in the schema */
  for (var field in testObject) {
    if (!this.definition[field] && !this.definition['*']) {
      if (this.strictness === 'strict') {
        errors.push({
          field: field,
          error: 'field not in definition'
        });
      } else if (this.strictness === 'loose') {
        result[field] = testObject[field];
      }
    }
  }

  if (errors.length) {
    this.lastErrors = errors;
  }

  /** If there is anything defined as an error - don't return the result! */
  if (this.strictness === 'strict' && errors.length) {
    result = false;
  }
  if (!errors.length) {
    errors = null;
  }

  delete testObject;

  if (callback) {
    callback(errors, result);
  }
  return result;
};

/** This is a seperated form validate, in order to only having it once */
ObjectSchema.prototype.runValidations = function(field, definition, testObject, errors) {
  var passed = true;

  if (definition.ignored) {
    return;
  }

  if ((this.strictness !== 'strict' || definition.optional) &&
    !definition.required &&
    (!testObject || typeof testObject[field] === 'undefined')) {

    if (definition.default) return definition.default;
    else return;
  }

  if (definition.objectSchema && typeof definition.objectSchema === 'object') {
    if (!(definition.objectSchema instanceof ObjectSchema)) {
      definition.objectSchema = new ObjectSchema(definition.objectSchema);
    }
    var subErrors = [];
    var result = definition.objectSchema.validate(testObject[field], subErrors);
    if (!result) {
      passed = false;
    } else {
      testObject[field] = result;
    }
    if (subErrors.length) {
      for (var i in subErrors) {
        subErrors[i].subField = subErrors[i].field;
        subErrors[i].field = field;
        errors.push(subErrors[i]);
      }
    }
  }

  /** Run theses before the tests, as these can render the value invalid */
  if (definition.filters) {
    this.definitionFilters(field, definition, testObject, errors);
  }
  if (definition.default) {
    this.definitionDefault(field, definition, testObject, errors);
  }

  if (definition.required) {
    if (!this.definitionRequired(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (definition.in) {
    if (!this.definitionIn(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (definition.type) {
    if (!this.definitionType(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (definition.instanceOf && !definition.objectSchema) {
    if (!this.definitionInstanceOf(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (definition.regex) {
    if (!this.definitionRegEx(field, definition, testObject, errors)) {
      passed = false;
    }
  }

  if (passed) {
    return testObject[field];
  }
};

/*************************\
 * DEFINITION VALIDATORS *
\*************************/
ObjectSchema.prototype.definitionFilters = function(field, definition, testObject, errors) {
  if (definition.filters) {
    if (definition.filters && typeof definition.filters !== 'object') {
      definition.filters = [definition.filters];
    }
    if (typeof definition.filters === 'object') {
      for (var i in definition.filters) {
        if (typeof definition.filters[i] === 'string') {
          var filterName = definition.filters[i];

          if (testObject[field][filterName] &&
            typeof testObject[field][filterName] === 'function') {
            testObject[field] = testObject[field][filterName]();

          } else if (this['filter' + filterName.upperCaseFirst()] &&
            typeof this['filter' + filterName.upperCaseFirst()] === 'function') {
            testObject[field] = this['filter' + filterName.upperCaseFirst()](field, testObject);
          }

        } else if (typeof definition.filters[i] === 'function') {
          testObject[field] = definition.filters[i](field, testObject);
        }
      }
    }
  }
};

/**
 * What is an empty value?
 * What it isn't: an empty value of the correct type...
 */
ObjectSchema.prototype.definitionDefault = function(field, definition, testObject, errors) {
  /** If no value is found, even after running filters, set the default */
  if (definition.default && typeof testObject[field] === 'undefined') {
    testObject[field] = definition.default;
  }
};

ObjectSchema.prototype.definitionRequired = function(field, definition, testObject, errors) {
  if (typeof testObject[field] === 'undefined') {
    errors.push({
      field: field,
      error: 'does not exist'
    });
    return false;
  }
  return true;
};

ObjectSchema.prototype.definitionIn = function(field, definition, testObject, errors) {
  if (typeof definition.in === 'object' && definition.in.length) {
    if (definition.in.indexOf(testObject[field]) === -1) {
      errors.push({
        field: field,
        error: 'value wasn\'t in array'
      });
      return false;
    }
  } else {
    return false;
  }
  return true;
};

ObjectSchema.prototype.definitionType = function(field, definition, testObject, errors) {
  if (typeof testObject[field] !== definition.type) {
    errors.push({
      field: field,
      error: 'wrong type'
    });
    return false;
  }
  return true;
};

ObjectSchema.prototype.definitionInstanceOf = function(field, definition, testObject, errors) {
  if (!(testObject[field] instanceof definition.instanceOf)) {
    errors.push({
      field: field,
      error: 'incorrect instance type'
    });
    return false;
  }
  return true;
};

ObjectSchema.prototype.definitionRegEx = function(field, definition, testObject, errors) {
  if (!testObject[field].match(definition.regex)) {
    errors.push({
      field: field,
      value: testObject[field],
      regex: definition.regex,
      error: 'value does not match regex'
    });
    return false;
  }
  return true;
};

/***********\
 * FILTERS *
\***********/
ObjectSchema.prototype.filterDate = function(field, dataObject) {
  if (dataObject[field]) {
    var date = new Date(dataObject[field]);
    if (date.toString() !== 'Invalid Date') {
      return date;
    }
  }
};

ObjectSchema.prototype.filterObjectID = ObjectSchema.prototype.filterObjectId = function(field, dataObject) {
  var objectId = dataObject[field];

  if (objectId instanceof ObjectId) {
    return objectId;
  }

  if (objectId.constructor.name.match(/^ObjectI(d|D)$/)) {
    return objectId;
  }

  if (ObjectId.isValid('' + objectId)) {
    return new ObjectId('' + objectId);
  }

  return objectId;
};

ObjectSchema.prototype.filterDbref = ObjectSchema.prototype.filterDbRef = ObjectSchema.prototype.filterDBRef = function(field, dataObject) {
  var dbRef = dataObject[field];

  if (dbRef instanceof DBRef) {
    return dbRef;
  }

  if (dbRef.constructor.name === 'DBRef') {
    return dbRef;
  }

  try {
    var namespace = dbRef.namespace || dbRef.collection || dbRef.$ref;
    var oid = dbRef.oid || dbRef._id || dbRef.id || dbRef.$id;
    var db = dbRef.db || dbRef.$db || dbRef.database || '';
    oid = ObjectId('' + oid);

    dbRef = new DBRef(namespace, oid, db);
  } catch ( e ) {}

  return dbRef;
};

/*************\
 * UTILITIES *
\*************/
ObjectSchema.prototype._mergeObjects = function() {
  var newObject = {};
  for (var objectIndex in arguments) {
    for (var objectKey in arguments[objectIndex]) {
      newObject[objectKey] = arguments[objectIndex][objectKey];
    }
  }
  return newObject;
};

<|endoftext|>


<commit_before>var request = require('request');
var Q = require("q");

var utils = require("soa-example-core-utils");

var config = require("soa-example-service-config").config();

var redisUtil = require('soa-example-redis-util');

var createProduct = function(accessToken, name){
	var deferred = Q.defer();

	var url = utils.createBaseUrl(config.productServiceIp, config.productServicePort);

	var object = {
		name: name
	};

	utils.postJsonWithAccessToken(accessToken, object, url + "/products").then(function(response){
		deferred.resolve(response);
	});

	return deferred.promise;
};

var getProductById = function(accessToken, id){
	var deferred = Q.defer();

	redisUtil.get(id).then(function(product){
		if ( product ){
			console.log("Product by ID found in Redis");
			deferred.resolve(product);
			return;
		}

		var url = utils.createBaseUrl(config.productServiceIp, config.productServicePort);

		utils.getWithAccessToken(accessToken, url + "/products/" + id).then(function(product){
			redisUtil.put(id, product);
			deferred.resolve(product);
		});
	});

	return deferred.promise;
};

var getProducts = function(accessToken){
	var deferred = Q.defer();

	var url = utils.createBaseUrl(config.productServiceIp, config.productServicePort);

	utils.getWithAccessToken(accessToken, url + "/products").then(function(products){
		deferred.resolve(products);
	});

	return deferred.promise;
};

module.exports = {
	createProduct: createProduct,
	getProductById: getProductById,
	getProducts: getProducts
};<commit_msg>updates<commit_after>var request = require('request');
var Q = require("q");

var utils = require("soa-example-core-utils");

var config = require("soa-example-service-config").config();

var redisUtil = require('soa-example-redis-util');

var createProduct = function(accessToken, name){
	var deferred = Q.defer();

	var url = utils.createBaseUrl(config.productServiceIp, config.productServicePort);

	var object = {
		name: name
	};

	utils.postJsonWithAccessToken(accessToken, object, url + "/products").then(function(response){
		deferred.resolve(response);
	});

	return deferred.promise;
};

var getProductById = function(accessToken, id){
	var deferred = Q.defer();

	redisUtil.get(id).then(function(product){
		if ( product ){
			console.log("Product by ID found in Redis");
			deferred.resolve(product);
			return;
		}

		var url = utils.createBaseUrl(config.productServiceIp, config.productServicePort);

		utils.getWithAccessToken(accessToken, url + "/products/" + id).then(function(product){
			redisUtil.put(id, product);
			deferred.resolve(product);
		});
	});

	return deferred.promise;
};

var getProducts = function(accessToken){
	var deferred = Q.defer();

	var url = utils.createBaseUrl(config.productServiceIp, config.productServicePort);

	utils.getWithAccessToken(accessToken, url + "/products").then(function(products){
		deferred.resolve(products);
	});

	return deferred.promise;
};

var getProductsByCategoryId = function(accessToken, categoryId){
	var deferred = Q.defer();

	var url = utils.createBaseUrl(config.productServiceIp, config.productServicePort);

	utils.getWithAccessToken(accessToken, url + "/products/category/" + categoryId).then(function(products){
		deferred.resolve(products);
	});

	return deferred.promise;
};

module.exports = {
	createProduct: createProduct,
	getProductById: getProductById,
	getProducts: getProducts,
	getProductsByCategoryId: getProductsByCategoryId
};<|endoftext|>


<commit_before>const carousel = document.querySelector('.carousel'),
queries = ['sky', 'sun+mountain', 'snow', 'sun+desert', 'sun+ocean', 'sun+forest', 'sun+winter', 'sun+field', 'autumn'],
initMeasurements = window.screen.availWidth + 'x' + window.screen.availWidth,
bubbleRow = document.querySelector('.bubble-row');
let startX;

adjCSS('img-width', window.innerWidth);
adjCSS('img-height', window.innerHeight);

// render marker for each query
queries.forEach(function(query) {
    addMarkerToHTML();
});

// render slide for each query
queries.forEach(function(query) {
    addSlideToHTML(query);
});

// init active class
setActive(document.querySelector('.slide'));
setActive(document.querySelector('.bubble'));

let slides = document.querySelectorAll('.slide'),
bubbles = document.querySelectorAll('.bubble');
[].forEach.call(bubbles, function(e){e.addEventListener('click', bubbleChange, true)});

// shifts two css variables on resize
window.addEventListener('resize', function() {
    adjCSS('img-width', window.innerWidth);
    adjCSS('img-height', window.innerHeight);
});

// make side arrow buttons work
const rightControl = document.querySelector('.go-right'),
leftControl = document.querySelector('.go-left');
rightControl.addEventListener('click', function() { moveSlide('right', slides) });
leftControl.addEventListener('click', function() { moveSlide('left', slides) });

// allow navigation with arrow keys
window.addEventListener('keydown', function(e) {
    if (e.keyCode == 39) moveSlide('right', slides);
    if (e.keyCode == 37) moveSlide('left', slides);
})

// initiate mobile UI
carousel.addEventListener("touchstart", touch2Mouse, true);
carousel.addEventListener("touchmove", touch2Mouse, true);
carousel.addEventListener("touchend", touch2Mouse, true);
carousel.addEventListener('mousedown', mousedownHandler, true);

function mousedownHandler(e) {
    startX = e.clientX;
    carousel.addEventListener('mouseup', mouseupHandler, true);
}
function mouseupHandler(e) {
    if (startX > e.clientX && e.clientX < window.innerWidth/2) {
        moveSlide('right', slides);
    }
    if (startX < e.clientX && e.clientX > window.innerWidth/2) {
        moveSlide('left', slides);
    }
}
function touch2Mouse(e) {
  var theTouch = e.changedTouches[0];
  var mouseEv;

  switch(e.type)
  {
    case "touchstart": mouseEv="mousedown"; break;
    case "touchend":   mouseEv="mouseup"; break;
    case "touchmove":  mouseEv="mousemove"; break;
    default: return;
  }

  var mouseEvent = document.createEvent("MouseEvent");
  mouseEvent.initMouseEvent(
      mouseEv, true, true, window, 1,
       theTouch.screenX, theTouch.screenY, theTouch.clientX, theTouch.clientY,
      false, false, false, false, 0, null
    );
  theTouch.target.dispatchEvent(mouseEvent);

  e.preventDefault();
}
function moveSlide(direction, target) {
    for (i = 0; i < target.length; i++) {
        if (target[i].classList.contains('active')) {

            // removes active from current slide
            target[i].classList.remove('active');

            // separates between right and left control functionality
            if (direction == 'right') {
                if (i == target.length-1) {
                    target[0].classList.add('active');
                    break;
                }
                target[i+1].classList.add('active');
                break;
            } else {
                if (i == 0) {
                    target[target.length-1].classList.add('active');
                    break;
                }
                target[i-1].classList.add('active');
                break;
            }

        }
    }
    if (target == slides) {
        moveSlide(direction, bubbles);
    }
}
function adjCSS(name, value) {
    document.documentElement.style.setProperty(`--${name}`, value + 'px');
}
function bubbleChange(e) {
    let node = e.target;
    document.querySelector('.bubble.active').classList.remove('active');
    document.querySelector('.slide.active').classList.remove('active');

    var i=0, e=node;
    while (e = e.previousSibling) { ++i;}

    node.classList.add('active');
    slides[i].classList.add('active');
}
function addSlideToHTML(query) {
    carousel.innerHTML += `<div class="slide" style="background: #222 url('http://source.unsplash.com/random/`+ initMeasurements + '?' + query + `') no-repeat center; background-size: cover;"></div>`;
}
function addMarkerToHTML() {
    bubbleRow.innerHTML += `<div class="bubble"><div class="marker"></div></div>`;
}
function setActive(target) {
    target.classList.add('active')
};<commit_msg>tightened js<commit_after>const carousel = document.querySelector('.carousel'),
queries = ['sky', 'sun+mountain', 'snow', 'sun+desert', 'sun+ocean', 'sun+forest', 'sun+winter', 'sun+field', 'autumn'],
initMeasurements = window.screen.availWidth + 'x' + window.screen.availHeight,
bubbleRow = document.querySelector('.bubble-row'),
rightControl = document.querySelector('.go-right'),
leftControl = document.querySelector('.go-left');

let startX;

setCSSVariables();
[].forEach.call(queries, function(e) { addMarkerToHTML(); addSlideToHTML(e) });
setActive('.slide');
setActive('.bubble');

const slides = document.querySelectorAll('.slide'),
bubbles = document.querySelectorAll('.bubble');
[].forEach.call(bubbles, function(e) { e.addEventListener('click', bubbleChange, true) });

window.addEventListener('resize', function() {
    setCSSVariables();
});

// make side arrow buttons work
rightControl.addEventListener('click', function() { moveSlide('right', slides) });
leftControl.addEventListener('click', function() { moveSlide('left', slides) });

// allow navigation with arrow keys
window.addEventListener('keydown', function(e) {
    if (e.keyCode == 39) moveSlide('right', slides);
    if (e.keyCode == 37) moveSlide('left', slides);
})

// initiate mobile UI
carousel.addEventListener("touchstart", touch2Mouse, true);
carousel.addEventListener("touchmove", touch2Mouse, true);
carousel.addEventListener("touchend", touch2Mouse, true);
carousel.addEventListener('mousedown', mousedownHandler, true);

function mousedownHandler(e) {
    startX = e.clientX;
    carousel.addEventListener('mouseup', mouseupHandler, true);
}
function mouseupHandler(e) {
    if (startX > e.clientX && e.clientX < window.innerWidth/2) {
        moveSlide('right', slides);
    }
    if (startX < e.clientX && e.clientX > window.innerWidth/2) {
        moveSlide('left', slides);
    }
}
function touch2Mouse(e) {
  var theTouch = e.changedTouches[0];
  var mouseEv;

  switch(e.type)
  {
    case "touchstart": mouseEv="mousedown"; break;
    case "touchend":   mouseEv="mouseup"; break;
    case "touchmove":  mouseEv="mousemove"; break;
    default: return;
  }

  var mouseEvent = document.createEvent("MouseEvent");
  mouseEvent.initMouseEvent(
      mouseEv, true, true, window, 1,
       theTouch.screenX, theTouch.screenY, theTouch.clientX, theTouch.clientY,
      false, false, false, false, 0, null
    );
  theTouch.target.dispatchEvent(mouseEvent);

  e.preventDefault();
}
function moveSlide(direction, target) {
    for (i = 0; i < target.length; i++) {
        if (target[i].classList.contains('active')) {

            // removes active from current slide
            target[i].classList.remove('active');

            // separates between right and left control functionality
            if (direction == 'right') {
                if (i == target.length-1) {
                    target[0].classList.add('active');
                    break;
                }
                target[i+1].classList.add('active');
                break;
            } else {
                if (i == 0) {
                    target[target.length-1].classList.add('active');
                    break;
                }
                target[i-1].classList.add('active');
                break;
            }

        }
    }
    if (target == slides) {
        moveSlide(direction, bubbles);
    }
}
function adjCSS(name, value) {
    document.documentElement.style.setProperty(`--${name}`, value + 'px');
}
function bubbleChange(e) {
    let node = e.target;
    document.querySelector('.bubble.active').classList.remove('active');
    document.querySelector('.slide.active').classList.remove('active');

    var i=0, e=node;
    while (e = e.previousSibling) { ++i;}

    node.classList.add('active');
    slides[i].classList.add('active');
}
function addSlideToHTML(query) {
    carousel.innerHTML += `<div class="slide" style="background: #222 url('http://source.unsplash.com/random/`+ initMeasurements + '?' + query + `') no-repeat center; background-size: cover;"></div>`;
}
function addMarkerToHTML() {
    bubbleRow.innerHTML += `<div class="bubble"><div class="marker"></div></div>`;
}
function setActive(selector) {
    const target = document.querySelector(selector);
    target.classList.add('active')
};
function setCSSVariables() {
    adjCSS('img-width', window.innerWidth);
    adjCSS('img-height', window.innerHeight);;
};<|endoftext|>


<commit_before>/* jshint node:true */

var _ = require('lodash');

module.exports = aposPreferences;

function aposPreferences(options, callback) {
  return new aposPreferences.Construct(options, callback);
}

aposPreferences.Construct = function(options, callback) {
  var self = this;

  // you need a schema since there isn't one by default!
  if(!options.schema) {
    throw new Error('apos-preferences: You need to specify a schema in order to use this module.');
  }

  // =================================================================
  // CONFIGURATION
  // =================================================================

  self._app = options.app;
  self._apos = options.apos;
  self._schemas = options.schemas;
  self._schema = self._schemas.compose(options.schema);

  self._preferences = self._apos.db.collection('aposPreferences');

  self._apos.mixinModuleAssets(self, 'aposPreferences', __dirname, options);
  self.pushAsset('script', 'editor', { when: 'user' });
  self.pushAsset('template', 'manage', { when: 'user', data: { fields: self._schema } });

  self._apos.addLocal('aposPreferencesMenu', function(permissions) {
    if (!permissions.admin) {
      // Oh heck no
      return '';
    }
    return self.render(__dirname + '/views/preferencesMenu', {});
  });

  self._apos.pushGlobalData({
    aposPreferences: {
      schema: self._schema
    }
  });

  options.modules = (options.modules || []).concat([ { dir: __dirname, name: 'events' } ]);

  // =================================================================
  // ROUTES
  // =================================================================

  self._app.get('/apos-preferences', function(req, res) {
    if((!req.user) || (!req.user.permissions.admin)) {
      res.statusCode = 403;
      return res.send();
    }
    self._preferences.find().toArray( function(err, results) {
      if (results.length) {
        return res.json( _.omit(results[0], '_id') );
      } else {
        return res.send({ status: 'ok', errors: 'No Preferences Results' });
      }
    });
  });

  self._app.post('/apos-preferences', function(req, res) {
    if((!req.user) || (!req.user.permissions.admin)) {
      res.statusCode = 403;
      return res.send();
    }
    var values = {};
    return self._schemas.convertFields(req, self._schema, 'form', req.body, values, function(err) {
      if (err) {
        return res.send({ status: 'Error', errors: err });
      } else {

        self._preferences.find().toArray( function(err, results) {
          var newPrefs = _.merge((results.length ? results[0] : {}), values );
          self._preferences.save(newPrefs, function(err) {
            return res.send({
              status: err ? 'Error' : 'ok',
              errors: err
            });
          });
        });
      }
    });
  });

  // Put the preferences in req.extras. If req.extras does not exist
  // it is created. This way the method can be called directly when
  // you're not generating an A2 page response, for instance from
  // an AJAX action.
  //
  // For bc reasons this method must be called self.loader, which makes
  // it a page loader. But we now also invoke it from middleware, to make
  // the data available in contexts like the notfound template. So there is
  // a built-in guard against loading twice.

  self.loader = function(req, callback) {
    if (req._aposPreferencesLoaded) {
      return setImmediate(callback);
    }
    req.extras = req.extras || {};
    self._preferences.find().toArray( function(err, results) {
      req._aposPreferencesLoaded = true;
      req.extras.preferences = {};
      if (err) {
        return callback(err);
      }
      if (results.length) {
        req.extras.preferences = _.omit(results[0], '_id');
      }
      return callback(null);
    });
  };

  self.middleware = [
    function(req, res, next) {
      return self.loader(req, function(err) {
        if (err) {
          res.statusCode = 500;
          req.template = 'serverError';
          return res.send(options.pages.renderPage(req, options.apos.options.rootDir + '/views/pages/serverError', {}, 'anon'));
        }
        return next();
      });
    }
  ];

  // Must wait at least until next tick to invoke callback!
  if (callback) {
    process.nextTick(function() { return callback(null); });
  }
};
<commit_msg>also log the error<commit_after>/* jshint node:true */

var _ = require('lodash');

module.exports = aposPreferences;

function aposPreferences(options, callback) {
  return new aposPreferences.Construct(options, callback);
}

aposPreferences.Construct = function(options, callback) {
  var self = this;

  // you need a schema since there isn't one by default!
  if(!options.schema) {
    throw new Error('apos-preferences: You need to specify a schema in order to use this module.');
  }

  // =================================================================
  // CONFIGURATION
  // =================================================================

  self._app = options.app;
  self._apos = options.apos;
  self._schemas = options.schemas;
  self._schema = self._schemas.compose(options.schema);

  self._preferences = self._apos.db.collection('aposPreferences');

  self._apos.mixinModuleAssets(self, 'aposPreferences', __dirname, options);
  self.pushAsset('script', 'editor', { when: 'user' });
  self.pushAsset('template', 'manage', { when: 'user', data: { fields: self._schema } });

  self._apos.addLocal('aposPreferencesMenu', function(permissions) {
    if (!permissions.admin) {
      // Oh heck no
      return '';
    }
    return self.render(__dirname + '/views/preferencesMenu', {});
  });

  self._apos.pushGlobalData({
    aposPreferences: {
      schema: self._schema
    }
  });

  options.modules = (options.modules || []).concat([ { dir: __dirname, name: 'events' } ]);

  // =================================================================
  // ROUTES
  // =================================================================

  self._app.get('/apos-preferences', function(req, res) {
    if((!req.user) || (!req.user.permissions.admin)) {
      res.statusCode = 403;
      return res.send();
    }
    self._preferences.find().toArray( function(err, results) {
      if (results.length) {
        return res.json( _.omit(results[0], '_id') );
      } else {
        return res.send({ status: 'ok', errors: 'No Preferences Results' });
      }
    });
  });

  self._app.post('/apos-preferences', function(req, res) {
    if((!req.user) || (!req.user.permissions.admin)) {
      res.statusCode = 403;
      return res.send();
    }
    var values = {};
    return self._schemas.convertFields(req, self._schema, 'form', req.body, values, function(err) {
      if (err) {
        return res.send({ status: 'Error', errors: err });
      } else {

        self._preferences.find().toArray( function(err, results) {
          var newPrefs = _.merge((results.length ? results[0] : {}), values );
          self._preferences.save(newPrefs, function(err) {
            return res.send({
              status: err ? 'Error' : 'ok',
              errors: err
            });
          });
        });
      }
    });
  });

  // Put the preferences in req.extras. If req.extras does not exist
  // it is created. This way the method can be called directly when
  // you're not generating an A2 page response, for instance from
  // an AJAX action.
  //
  // For bc reasons this method must be called self.loader, which makes
  // it a page loader. But we now also invoke it from middleware, to make
  // the data available in contexts like the notfound template. So there is
  // a built-in guard against loading twice.

  self.loader = function(req, callback) {
    if (req._aposPreferencesLoaded) {
      return setImmediate(callback);
    }
    req.extras = req.extras || {};
    self._preferences.find().toArray( function(err, results) {
      req._aposPreferencesLoaded = true;
      req.extras.preferences = {};
      if (err) {
        return callback(err);
      }
      if (results.length) {
        req.extras.preferences = _.omit(results[0], '_id');
      }
      return callback(null);
    });
  };

  self.middleware = [
    function(req, res, next) {
      return self.loader(req, function(err) {
        if (err) {
          console.error(err);
          res.statusCode = 500;
          req.template = 'serverError';
          return res.send(options.pages.renderPage(req, options.apos.options.rootDir + '/views/pages/serverError', {}, 'anon'));
        }
        return next();
      });
    }
  ];

  // Must wait at least until next tick to invoke callback!
  if (callback) {
    process.nextTick(function() { return callback(null); });
  }
};
<|endoftext|>


<commit_before>var React = require('react-native')
var { PanResponder, View, StyleSheet, Dimensions, PropTypes } = React
var deviceScreen = Dimensions.get('window')
var tween = require('./Tweener')

var drawer = React.createClass({

  _left: 0,
  _prevLeft: 0,
  _offsetOpen: 0,
  _offsetClosed: 0,
  _open: false,
  _panning: false,
  _tweenPending: false,
  _lastPress: 0,
  _panStartTime: 0,
  _syncAfterUpdate: false,

  statics: {
    tweenPresets: {
      parallax: (ratio, side = 'left') => {
        var drawer = {}
        drawer[side] = -150*(1-ratio)
        return { drawer: drawer }
      }
    }
  },

  propTypes: {
    type: React.PropTypes.string,
    closedDrawerOffset: React.PropTypes.number,
    openDrawerOffset: React.PropTypes.number,
    openDrawerThreshold: React.PropTypes.number,
    relativeDrag: React.PropTypes.bool,
    panStartCompensation: React.PropTypes.bool,
    panOpenMask: React.PropTypes.number,
    panCloseMask: React.PropTypes.number,
    captureGestures: React.PropTypes.bool,
    negotiatePan: React.PropTypes.bool,
    initializeOpen: React.PropTypes.bool,
    tweenHandler: React.PropTypes.func,
    tweenDuration: React.PropTypes.number,
    tweenEasing: React.PropTypes.string,
    disabled: React.PropTypes.bool,
    acceptDoubleTap: React.PropTypes.bool,
    acceptTap: React.PropTypes.bool,
    acceptPan: React.PropTypes.bool,
    tapToClose: React.PropTypes.bool,
    styles: React.PropTypes.object,
    onOpen: React.PropTypes.func,
    onOpenStart: React.PropTypes.func,
    onClose: React.PropTypes.func,
    onCloseStart: React.PropTypes.func,
    side: React.PropTypes.oneOf(['left', 'right']),
  },

  getDefaultProps () {
    return {
      type: 'displace',
      closedDrawerOffset: 0,
      openDrawerOffset: 0,
      openDrawerThreshold: .25,
      relativeDrag: true,
      panStartCompensation: true,
      panOpenMask: .25,
      panCloseMask: .25,
      captureGestures: false,
      negotiatePan: false,
      initializeOpen: false,
      tweenHandler: null,
      tweenDuration: 250,
      tweenEasing: 'linear',
      disabled: false,
      acceptDoubleTap: false,
      acceptTap: false,
      acceptPan: true,
      tapToClose: false,
      styles: {},
      onOpen: () => {},
      onClose: () => {},
      side: 'left',
    }
  },

  childContextTypes: {
    drawer: PropTypes.any
  },

  getChildContext () {
    return {
      drawer: this
    }
  },

  getInitialState () {
    return { viewport: deviceScreen }
  },

  setViewport (e) {
    var viewport = e.nativeEvent.layout
    var oldViewport = this.state.viewport
    if(viewport.width === oldViewport.width && viewport.height === oldViewport.height){
      return
    }
    this.resync(viewport)
  },

  resync (viewport, props) {
    if (viewport) this._syncAfterUpdate = true
    var viewport = viewport || this.state.viewport
    var props = props || this.props
    this._offsetClosed = props.closedDrawerOffset%1 === 0 ? props.closedDrawerOffset : props.closedDrawerOffset * viewport.width
    this._offsetOpen = props.openDrawerOffset%1 === 0 ? props.openDrawerOffset : props.openDrawerOffset * viewport.width
    this.setState({ viewport: viewport })
  },

  propsWhomRequireUpdate: [
    'closedDrawerOffset',
    'openDrawerOffset',
    'type'
  ],

  requiresResync (nextProps) {
    for (var i = 0; i < this.propsWhomRequireUpdate.length; i++) {
      var key = this.propsWhomRequireUpdate[i]
      if(this.props[key] !== nextProps[key]){ return true }
    }
  },

  componentWillReceiveProps (nextProps) {
    if(this.requiresResync(nextProps)){
      this.resync(null, nextProps)
    }
  },

  initialize (props) {
    var fullWidth = this.state.viewport.width
    this._offsetClosed = props.closedDrawerOffset%1 === 0 ? props.closedDrawerOffset : props.closedDrawerOffset*fullWidth
    this._offsetOpen = props.openDrawerOffset%1 === 0 ? props.openDrawerOffset : props.openDrawerOffset*fullWidth
    this._prevLeft = this._left

    var styles = {
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
      },
    }
    styles.main = Object.assign({
        position: 'absolute',
        top: 0,
        height: this.state.viewport.height,
      }, {borderWidth:0}, this.props.styles.main)
    styles.drawer = Object.assign({
        position: 'absolute',
        top: 0,
        height: this.state.viewport.height,
      }, {borderWidth:0}, this.props.styles.drawer)

    if (props.initializeOpen === true) { // open
      this._open = true
      this._left = fullWidth - this._offsetOpen
      styles.main[this.props.side] = 0
      styles.drawer[this.props.side] = 0
      if(props.type === 'static') styles.main[this.props.side] = fullWidth - this._offsetOpen
      if(props.type === 'displace') styles.main[this.props.side] = fullWidth - this._offsetOpen
    } else { // closed
      this._open = false
      this._left = this._offsetClosed
      styles.main[this.props.side] = this._offsetClosed
      if(props.type === 'static') styles.drawer[this.props.side] = 0
      if(props.type === 'overlay') styles.drawer[this.props.side] = this._offsetClosed + this._offsetOpen - fullWidth
      if(props.type === 'displace') styles.drawer[this.props.side] = - fullWidth + this._offsetClosed + this._offsetOpen
    }

    if (this.refs.main) {
      this.refs.drawer.setNativeProps({ style: {left: styles.drawer.left}})
      this.refs.main.setNativeProps({ style: {left: styles.main.left}})
    } else {
      this.stylesheet = StyleSheet.create(styles)
      this.responder = PanResponder.create({
        onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
        onStartShouldSetPanResponderCapture: this.handleStartShouldSetPanResponderCapture,
        onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
        onMoveShouldSetPanResponderCapture: this.handleMoveShouldSetPanResponderCapture,
        onPanResponderMove: this.handlePanResponderMove,
        onPanResponderRelease: this.handlePanResponderEnd,
      })
    }

    this.resync(null, props)
  },

  componentWillMount () {
    this.initialize(this.props)
  },

  componentDidUpdate () {
    if(this._syncAfterUpdate){
      this._syncAfterUpdate = false
      this._open ? this.open() : this.close()
    }
  },

  updatePosition () {
    var mainProps = {}
    var drawerProps = {}

    var ratio = (this._left-this._offsetClosed) / (this.getOpenLeft()-this._offsetClosed)

    switch (this.props.type) {
      case 'overlay':
        drawerProps[this.props.side] = -this.state.viewport.width+this._offsetOpen+this._left
        mainProps[this.props.side] = this._offsetClosed
        break
      case 'static':
        mainProps[this.props.side] = this._left
        drawerProps[this.props.side] = 0
        break
      case 'displace':
        mainProps[this.props.side] = this._left
        drawerProps[this.props.side] = -this.state.viewport.width+this._left+this._offsetOpen
        break
    }

    if (this.props.tweenHandler) {
      var propsFrag = this.props.tweenHandler(ratio, this.props.side)
      mainProps = Object.assign(mainProps, propsFrag.main)
      drawerProps = Object.assign(drawerProps, propsFrag.drawer)
    }
    this.refs.drawer.setNativeProps({style: drawerProps})
    this.refs.main.setNativeProps({style: mainProps})
  },

  shouldOpenDrawer (dx) {
    if(this._open){
      return dx < this.state.viewport.width*this.props.openDrawerThreshold
    }
    else{
      return dx > this.state.viewport.width*this.props.openDrawerThreshold
    }
  },

  handleStartShouldSetPanResponderCapture (e, gestureState) {
    if(this.props.captureGestures) return this.handleStartShouldSetPanResponder(e, gestureState)
    return false
  },

  handleStartShouldSetPanResponder (e, gestureState) {
    if (this.props.negotiatePan) return false
    this._panStartTime = Date.now()
    if(!this.testPanResponderMask(e, gestureState)){
      return false
    }
    return true
  },

  handleMoveShouldSetPanResponderCapture (e, gestureState) {
    if (this.props.captureGestures && this.props.negotiatePan) return this.handleMoveShouldSetPanResponder(e, gestureState)
    return false
  },

  handleMoveShouldSetPanResponder (e, gestureState) {
    if (!this.props.negotiatePan) return false
    var swipeToLeft = (gestureState.dx < 0) ? true : false;
    var swipeToRight = (gestureState.dx > 0) ? true : false;
    var swipeUpDown = (Math.abs(gestureState.dy) >= Math.abs(gestureState.dx)) ? true : false;
    var swipeInCloseDirection = (this.props.side == 'left') ? swipeToLeft: swipeToRight;
    if(swipeUpDown || (this._open && !swipeInCloseDirection) || (!this._open && swipeInCloseDirection)) return false
    return true
  },

  processTapGestures () {
    if (this.props.acceptTap) this._open ? this.close() : this.open()
    if (this.props.tapToClose && this._open) this.close()
    if (this.props.acceptDoubleTap) {
      var now = new Date().getTime()
      if(now - this._lastPress < 500){
        this._open ? this.close() : this.open()
      }
      this._lastPress = now
    }
  },

  testPanResponderMask (e, gestureState) {
    if(this.props.disabled){ return false }
    var x0 = e.nativeEvent.pageX

    var deltaOpen = this.props.side === 'left' ? deviceScreen.width - x0 : x0
    var deltaClose = this.props.side === 'left' ? x0 : deviceScreen.width - x0

    var whenClosedMask = this.props.panOpenMask % 1 === 0 && this.props.panOpenMask > 1 ? this.props.panOpenMask : deviceScreen.width*this.props.panOpenMask
    var whenOpenMask = this.props.panCloseMask % 1 === 0 && this.props.panCloseMask > 1 ? this.props.panCloseMask : deviceScreen.width*this.props.panCloseMask
    if( this._open && deltaOpen > whenOpenMask ) return false
    if( !this._open && deltaClose > whenClosedMask ) return false
    return true
  },

  handlePanResponderMove (e, gestureState) {
    if(!this.props.acceptPan){
      return false
    }

    //Math is ugly overly verbose here, probably can be greatly cleaned up
    var dx = gestureState.dx
    //@TODO store adjustedDx max so that it does not uncompensate when panning back
    var dx = gestureState.dx
    //Do nothing if we are panning the wrong way
    if(this._open ^ dx < 0 ^ this.props.side === 'right'){ return false }

    var absDx = Math.abs(dx)
    var moveX = gestureState.moveX
    var relMoveX = this.props.side === 'left'
      ? this._open ? -this.state.viewport.width + moveX : moveX
      : this._open ? -moveX : this.state.viewport.width - moveX
    var delta = relMoveX - dx
    var factor = absDx/Math.abs(relMoveX)
    var adjustedDx = dx + delta*factor
    var left = this.props.panStartCompensation ? this._prevLeft + adjustedDx : this._prevLeft + dx
    left = Math.min(left, this.getOpenLeft())
    left = Math.max(left, this.getClosedLeft())
    this._left = left
    this.updatePosition()
    this._panning = true
  },

  open () {
    this.props.onOpenStart && this.props.onOpenStart()
    tween({
      start: this._left,
      end: this.getOpenLeft(),
      duration: this.props.tweenDuration,
      easingType: this.props.tweenEasing,
      onFrame: (tweenValue) => {
        this._left = tweenValue
        this.updatePosition()
      },
      onEnd: () => {
        this._open = true
        this._prevLeft = this._left
        this.props.onOpen()
      }
    })
  },

  close () {
    this.props.onCloseStart && this.props.onCloseStart()
    tween({
      start: this._left,
      end: this.getClosedLeft(),
      easingType: this.props.tweenEasing,
      duration: this.props.tweenDuration,
      onFrame: (tweenValue) => {
        this._left = tweenValue
        this.updatePosition()
      },
      onEnd: () => {
        this._open = false
        this._prevLeft = this._left
        this.props.onClose()
      }
    })
  },

  toggle () {
    this._open ? this.close() : this.open()
  },

  handlePanResponderEnd (e, gestureState) {
    if (gestureState.moveX < 25 && (Date.now() - this._panStartTime < 500)) {
      this._panning = false
      this.processTapGestures()
      return
    }

    var absRelMoveX = this.props.side === 'left'
      ? this._open ? this.state.viewport.width - gestureState.moveX : gestureState.moveX
      : this._open ? gestureState.moveX : this.state.viewport.width - gestureState.moveX
    var calcPos = this.props.relativeDrag ? Math.abs(gestureState.dx) : absRelMoveX

    this.shouldOpenDrawer(calcPos) ? this.open() : this.close()

    this.updatePosition()
    this._prevLeft = this._left
    this._panning = false
  },

  getMainView () {
    return (
      <View
        key="main"
        style={[this.stylesheet.main, {width: this.getMainWidth(), height: this.state.viewport.height}]}
        ref="main"
        {...this.responder.panHandlers}>
        {this.props.children}
      </View>
    )
  },

  getDrawerView () {
    return (
      <View
        key="drawer"
        style={[this.stylesheet.drawer, {width: this.getDrawerWidth(), height: this.state.viewport.height}]}
        ref="drawer"
        {...this.responder.panHandlers}>
        {this.props.content}
      </View>
    )
  },

  render () {
    var first = this.props.type === 'overlay' ? this.getMainView() : this.getDrawerView()
    var second = this.props.type === 'overlay' ? this.getDrawerView() : this.getMainView()

    return (
      <View style={this.stylesheet.container} key="drawerContainer" onLayout={this.setViewport}>
        {first}
        {second}
      </View>
    )
  },

  getOpenLeft () {
    return this.state.viewport.width - this._offsetOpen
  },

  getClosedLeft () {
    return this._offsetClosed
  },

  getMainWidth () {
    return this.state.viewport.width - this._offsetClosed
  },

  getDrawerWidth () {
    return this.state.viewport.width - this._offsetOpen
  }

})

module.exports = drawer
<commit_msg>up tap threshold for 9.2<commit_after>var React = require('react-native')
var { PanResponder, View, StyleSheet, Dimensions, PropTypes } = React
var deviceScreen = Dimensions.get('window')
var tween = require('./Tweener')

var drawer = React.createClass({

  _left: 0,
  _prevLeft: 0,
  _offsetOpen: 0,
  _offsetClosed: 0,
  _open: false,
  _panning: false,
  _tweenPending: false,
  _lastPress: 0,
  _panStartTime: 0,
  _syncAfterUpdate: false,

  statics: {
    tweenPresets: {
      parallax: (ratio, side = 'left') => {
        var drawer = {}
        drawer[side] = -150*(1-ratio)
        return { drawer: drawer }
      }
    }
  },

  propTypes: {
    type: React.PropTypes.string,
    closedDrawerOffset: React.PropTypes.number,
    openDrawerOffset: React.PropTypes.number,
    openDrawerThreshold: React.PropTypes.number,
    relativeDrag: React.PropTypes.bool,
    panStartCompensation: React.PropTypes.bool,
    panOpenMask: React.PropTypes.number,
    panCloseMask: React.PropTypes.number,
    captureGestures: React.PropTypes.bool,
    negotiatePan: React.PropTypes.bool,
    initializeOpen: React.PropTypes.bool,
    tweenHandler: React.PropTypes.func,
    tweenDuration: React.PropTypes.number,
    tweenEasing: React.PropTypes.string,
    disabled: React.PropTypes.bool,
    acceptDoubleTap: React.PropTypes.bool,
    acceptTap: React.PropTypes.bool,
    acceptPan: React.PropTypes.bool,
    tapToClose: React.PropTypes.bool,
    styles: React.PropTypes.object,
    onOpen: React.PropTypes.func,
    onOpenStart: React.PropTypes.func,
    onClose: React.PropTypes.func,
    onCloseStart: React.PropTypes.func,
    side: React.PropTypes.oneOf(['left', 'right']),
  },

  getDefaultProps () {
    return {
      type: 'displace',
      closedDrawerOffset: 0,
      openDrawerOffset: 0,
      openDrawerThreshold: .25,
      relativeDrag: true,
      panStartCompensation: true,
      panOpenMask: .25,
      panCloseMask: .25,
      captureGestures: false,
      negotiatePan: false,
      initializeOpen: false,
      tweenHandler: null,
      tweenDuration: 250,
      tweenEasing: 'linear',
      disabled: false,
      acceptDoubleTap: false,
      acceptTap: false,
      acceptPan: true,
      tapToClose: false,
      styles: {},
      onOpen: () => {},
      onClose: () => {},
      side: 'left',
    }
  },

  childContextTypes: {
    drawer: PropTypes.any
  },

  getChildContext () {
    return {
      drawer: this
    }
  },

  getInitialState () {
    return { viewport: deviceScreen }
  },

  setViewport (e) {
    var viewport = e.nativeEvent.layout
    var oldViewport = this.state.viewport
    if(viewport.width === oldViewport.width && viewport.height === oldViewport.height){
      return
    }
    this.resync(viewport)
  },

  resync (viewport, props) {
    if (viewport) this._syncAfterUpdate = true
    var viewport = viewport || this.state.viewport
    var props = props || this.props
    this._offsetClosed = props.closedDrawerOffset%1 === 0 ? props.closedDrawerOffset : props.closedDrawerOffset * viewport.width
    this._offsetOpen = props.openDrawerOffset%1 === 0 ? props.openDrawerOffset : props.openDrawerOffset * viewport.width
    this.setState({ viewport: viewport })
  },

  propsWhomRequireUpdate: [
    'closedDrawerOffset',
    'openDrawerOffset',
    'type'
  ],

  requiresResync (nextProps) {
    for (var i = 0; i < this.propsWhomRequireUpdate.length; i++) {
      var key = this.propsWhomRequireUpdate[i]
      if(this.props[key] !== nextProps[key]){ return true }
    }
  },

  componentWillReceiveProps (nextProps) {
    if(this.requiresResync(nextProps)){
      this.resync(null, nextProps)
    }
  },

  initialize (props) {
    var fullWidth = this.state.viewport.width
    this._offsetClosed = props.closedDrawerOffset%1 === 0 ? props.closedDrawerOffset : props.closedDrawerOffset*fullWidth
    this._offsetOpen = props.openDrawerOffset%1 === 0 ? props.openDrawerOffset : props.openDrawerOffset*fullWidth
    this._prevLeft = this._left

    var styles = {
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
      },
    }
    styles.main = Object.assign({
        position: 'absolute',
        top: 0,
        height: this.state.viewport.height,
      }, {borderWidth:0}, this.props.styles.main)
    styles.drawer = Object.assign({
        position: 'absolute',
        top: 0,
        height: this.state.viewport.height,
      }, {borderWidth:0}, this.props.styles.drawer)

    if (props.initializeOpen === true) { // open
      this._open = true
      this._left = fullWidth - this._offsetOpen
      styles.main[this.props.side] = 0
      styles.drawer[this.props.side] = 0
      if(props.type === 'static') styles.main[this.props.side] = fullWidth - this._offsetOpen
      if(props.type === 'displace') styles.main[this.props.side] = fullWidth - this._offsetOpen
    } else { // closed
      this._open = false
      this._left = this._offsetClosed
      styles.main[this.props.side] = this._offsetClosed
      if(props.type === 'static') styles.drawer[this.props.side] = 0
      if(props.type === 'overlay') styles.drawer[this.props.side] = this._offsetClosed + this._offsetOpen - fullWidth
      if(props.type === 'displace') styles.drawer[this.props.side] = - fullWidth + this._offsetClosed + this._offsetOpen
    }

    if (this.refs.main) {
      this.refs.drawer.setNativeProps({ style: {left: styles.drawer.left}})
      this.refs.main.setNativeProps({ style: {left: styles.main.left}})
    } else {
      this.stylesheet = StyleSheet.create(styles)
      this.responder = PanResponder.create({
        onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
        onStartShouldSetPanResponderCapture: this.handleStartShouldSetPanResponderCapture,
        onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
        onMoveShouldSetPanResponderCapture: this.handleMoveShouldSetPanResponderCapture,
        onPanResponderMove: this.handlePanResponderMove,
        onPanResponderRelease: this.handlePanResponderEnd,
      })
    }

    this.resync(null, props)
  },

  componentWillMount () {
    this.initialize(this.props)
  },

  componentDidUpdate () {
    if(this._syncAfterUpdate){
      this._syncAfterUpdate = false
      this._open ? this.open() : this.close()
    }
  },

  updatePosition () {
    var mainProps = {}
    var drawerProps = {}

    var ratio = (this._left-this._offsetClosed) / (this.getOpenLeft()-this._offsetClosed)

    switch (this.props.type) {
      case 'overlay':
        drawerProps[this.props.side] = -this.state.viewport.width+this._offsetOpen+this._left
        mainProps[this.props.side] = this._offsetClosed
        break
      case 'static':
        mainProps[this.props.side] = this._left
        drawerProps[this.props.side] = 0
        break
      case 'displace':
        mainProps[this.props.side] = this._left
        drawerProps[this.props.side] = -this.state.viewport.width+this._left+this._offsetOpen
        break
    }

    if (this.props.tweenHandler) {
      var propsFrag = this.props.tweenHandler(ratio, this.props.side)
      mainProps = Object.assign(mainProps, propsFrag.main)
      drawerProps = Object.assign(drawerProps, propsFrag.drawer)
    }
    this.refs.drawer.setNativeProps({style: drawerProps})
    this.refs.main.setNativeProps({style: mainProps})
  },

  shouldOpenDrawer (dx) {
    if(this._open){
      return dx < this.state.viewport.width*this.props.openDrawerThreshold
    }
    else{
      return dx > this.state.viewport.width*this.props.openDrawerThreshold
    }
  },

  handleStartShouldSetPanResponderCapture (e, gestureState) {
    if(this.props.captureGestures) return this.handleStartShouldSetPanResponder(e, gestureState)
    return false
  },

  handleStartShouldSetPanResponder (e, gestureState) {
    if (this.props.negotiatePan) return false
    this._panStartTime = Date.now()
    if(!this.testPanResponderMask(e, gestureState)){
      return false
    }
    return true
  },

  handleMoveShouldSetPanResponderCapture (e, gestureState) {
    if (this.props.captureGestures && this.props.negotiatePan) return this.handleMoveShouldSetPanResponder(e, gestureState)
    return false
  },

  handleMoveShouldSetPanResponder (e, gestureState) {
    if (!this.props.negotiatePan) return false
    var swipeToLeft = (gestureState.dx < 0) ? true : false;
    var swipeToRight = (gestureState.dx > 0) ? true : false;
    var swipeUpDown = (Math.abs(gestureState.dy) >= Math.abs(gestureState.dx)) ? true : false;
    var swipeInCloseDirection = (this.props.side == 'left') ? swipeToLeft: swipeToRight;
    if(swipeUpDown || (this._open && !swipeInCloseDirection) || (!this._open && swipeInCloseDirection)) return false
    return true
  },

  processTapGestures () {
    if (this.props.acceptTap) this._open ? this.close() : this.open()
    if (this.props.tapToClose && this._open) this.close()
    if (this.props.acceptDoubleTap) {
      var now = new Date().getTime()
      if(now - this._lastPress < 500){
        this._open ? this.close() : this.open()
      }
      this._lastPress = now
    }
  },

  testPanResponderMask (e, gestureState) {
    if(this.props.disabled){ return false }
    var x0 = e.nativeEvent.pageX

    var deltaOpen = this.props.side === 'left' ? deviceScreen.width - x0 : x0
    var deltaClose = this.props.side === 'left' ? x0 : deviceScreen.width - x0

    var whenClosedMask = this.props.panOpenMask % 1 === 0 && this.props.panOpenMask > 1 ? this.props.panOpenMask : deviceScreen.width*this.props.panOpenMask
    var whenOpenMask = this.props.panCloseMask % 1 === 0 && this.props.panCloseMask > 1 ? this.props.panCloseMask : deviceScreen.width*this.props.panCloseMask
    if( this._open && deltaOpen > whenOpenMask ) return false
    if( !this._open && deltaClose > whenClosedMask ) return false
    return true
  },

  handlePanResponderMove (e, gestureState) {
    if(!this.props.acceptPan){
      return false
    }

    //Math is ugly overly verbose here, probably can be greatly cleaned up
    var dx = gestureState.dx
    //@TODO store adjustedDx max so that it does not uncompensate when panning back
    var dx = gestureState.dx
    //Do nothing if we are panning the wrong way
    if(this._open ^ dx < 0 ^ this.props.side === 'right'){ return false }

    var absDx = Math.abs(dx)
    var moveX = gestureState.moveX
    var relMoveX = this.props.side === 'left'
      ? this._open ? -this.state.viewport.width + moveX : moveX
      : this._open ? -moveX : this.state.viewport.width - moveX
    var delta = relMoveX - dx
    var factor = absDx/Math.abs(relMoveX)
    var adjustedDx = dx + delta*factor
    var left = this.props.panStartCompensation ? this._prevLeft + adjustedDx : this._prevLeft + dx
    left = Math.min(left, this.getOpenLeft())
    left = Math.max(left, this.getClosedLeft())
    this._left = left
    this.updatePosition()
    this._panning = true
  },

  open () {
    this.props.onOpenStart && this.props.onOpenStart()
    tween({
      start: this._left,
      end: this.getOpenLeft(),
      duration: this.props.tweenDuration,
      easingType: this.props.tweenEasing,
      onFrame: (tweenValue) => {
        this._left = tweenValue
        this.updatePosition()
      },
      onEnd: () => {
        this._open = true
        this._prevLeft = this._left
        this.props.onOpen()
      }
    })
  },

  close () {
    this.props.onCloseStart && this.props.onCloseStart()
    tween({
      start: this._left,
      end: this.getClosedLeft(),
      easingType: this.props.tweenEasing,
      duration: this.props.tweenDuration,
      onFrame: (tweenValue) => {
        this._left = tweenValue
        this.updatePosition()
      },
      onEnd: () => {
        this._open = false
        this._prevLeft = this._left
        this.props.onClose()
      }
    })
  },

  toggle () {
    this._open ? this.close() : this.open()
  },

  handlePanResponderEnd (e, gestureState) {
    // @TODO fine tune these thresholds
    if (gestureState.moveX < 100 && (Date.now() - this._panStartTime < 500)) {
      this._panning = false
      this.processTapGestures()
      return
    }

    var absRelMoveX = this.props.side === 'left'
      ? this._open ? this.state.viewport.width - gestureState.moveX : gestureState.moveX
      : this._open ? gestureState.moveX : this.state.viewport.width - gestureState.moveX
    var calcPos = this.props.relativeDrag ? Math.abs(gestureState.dx) : absRelMoveX

    this.shouldOpenDrawer(calcPos) ? this.open() : this.close()

    this.updatePosition()
    this._prevLeft = this._left
    this._panning = false
  },

  getMainView () {
    return (
      <View
        key="main"
        style={[this.stylesheet.main, {width: this.getMainWidth(), height: this.state.viewport.height}]}
        ref="main"
        {...this.responder.panHandlers}>
        {this.props.children}
      </View>
    )
  },

  getDrawerView () {
    return (
      <View
        key="drawer"
        style={[this.stylesheet.drawer, {width: this.getDrawerWidth(), height: this.state.viewport.height}]}
        ref="drawer"
        {...this.responder.panHandlers}>
        {this.props.content}
      </View>
    )
  },

  render () {
    var first = this.props.type === 'overlay' ? this.getMainView() : this.getDrawerView()
    var second = this.props.type === 'overlay' ? this.getDrawerView() : this.getMainView()

    return (
      <View style={this.stylesheet.container} key="drawerContainer" onLayout={this.setViewport}>
        {first}
        {second}
      </View>
    )
  },

  getOpenLeft () {
    return this.state.viewport.width - this._offsetOpen
  },

  getClosedLeft () {
    return this._offsetClosed
  },

  getMainWidth () {
    return this.state.viewport.width - this._offsetClosed
  },

  getDrawerWidth () {
    return this.state.viewport.width - this._offsetOpen
  }

})

module.exports = drawer
<|endoftext|>