<commit_before>var registerComponent = function(tag, template) {
    Vue.component(tag, {
        template: template,
        props: ['data']
    });
};

var ALL_COMPONENTS =    '<weave-switch v-else-if="data.type == \'switch\'" v-bind:data="data"></weave-switch>\n' +
                        '<weave-loop v-else-if="data.type == \'loop\'" v-bind:data="data"></weave-loop>\n' +
                        '<weave-condition v-else-if="data.type == \'condition\'" v-bind:data="data"></weave-condition>\n';

var registerCoreComponents = _.once(function() {
    ALL_COMPONENTS = $("#template-all-components").html() + ALL_COMPONENTS;
    var coreComponents = [
        [
            'weave-switch',
            '
' +
            '    <all-components v-for="(value, key) in data.cases" ' +
            '                    v-if="key == $root.variables[data.variable]"' +
            '                    v-bind:data="value"></all-components>' +
            '
'
        ],
        [
            'weave-condition',
            '<all-components v-if="$root.evaluateCondition(data)"' +
            '                v-bind:data="data.value"></all-components>'
        ],
        [
            'weave-loop',
            '
' +
            '
' +
            '        <all-components v-bind:data="$root.processUITemplate(data.template, item)">' +
            '        </all-components>' +
            '
' +
            '
'
        ],
        [
            'all-components',
            ALL_COMPONENTS
        ]
    ];

    $("#template-all-components").html(ALL_COMPONENTS);
    coreComponents.forEach(function(item) {
        registerComponent.apply(null, item);
    });
});

function ExpressionEvaluator(app, contextObj, options) {
    var defaultOptions = {
        assumeIntermediateObjects: false
    }

    var curOpts = Object.assign({}, defaultOptions, options);
    contextObj.variables = app.variables;
    return function evaluate(template) {
        if (typeof template === "object" && Array.isArray(template)) {
            return template.map(function(item) {
                return evaluate(item);
            });
        } else if (typeof template === "object") {
            if (template.__vartype && template.__expression) {
                var context = contextObj[template.__vartype];
                if (context === undefined) {
                    return null;
                }

                return (template.__expression.keys || []).reduce(function(state, value) {
                    if (curOpts.assumeIntermediateObjects && state[value] == undefined) {
                        state[value] = {};
                    }
                    return state[value];
                }, context);
            }
            var result = {};
            Object.keys(template).forEach(function(key) {
                result[key] = evaluate(template[key], context);
            });
            return result;
        } else {
            return template;
        }
    };
}

function ConditionEvaluator(app, context, options) {
    var expressionEvaluator = ExpressionEvaluator(app, context, options);

    function equals(obj1, obj2) {
        return _.isEqual(expressionEvaluator(obj1), expressionEvaluator(obj2));
    }

    var comparators = {
        "eq": {fn: equals, args: 2}
    };

    return function evaluate(condition) {
        var comparatorInfo = comparators[condition.op];
        if (comparatorInfo === undefined) {
            console.log("Invalid operator: " + condition.op);
            return false;
        }
        if (comparatorInfo.args != condition.operands.length) {
            console.log("Bad number of operands")
            return false;
        }

        return comparatorInfo.fn.apply(null, condition.operands);
    };
}

function Actions(app, actions) {
    function rpc(action, context) {
        action.data.args = action.data.args || [];
        action.data.kwargs = action.data.kwargs || {};
        var data = ExpressionEvaluator(app, {result: context})(action.data);
        return $.ajax({
            url: "/api/rpc",
            type: 'post',
            contentType: 'application/json; charset=UTF-8',
            data: JSON.stringify(data)
        });
    }

    function store(data, context) {
        if (data.keys && data.keys.length) {
            var obj = (data.keys || []).slice(0, -1).reduce(function(state, value) {
                if (state[value] == undefined) {
                    state[value] = {};
                }
                return state[value];
            }, app);
            var result = ExpressionEvaluator(app, {result: context})(data.value);
            app.$set(obj, data.keys.slice(-1)[0], result);
        }
        return $.Deferred().resolve(data).promise();
    }

    function action(data, context) {
        if (data.action) {
            fireAction(data.action, data.data);
        }
        return $.Deferred().resolve(null).promise();
    }

    var handlers = {
        $rpc: rpc,
        $store: store,
        $action: action
    };

    function evaluate(action, context) {
        var handler = handlers[action.type];
        if (handler === undefined) {
            console.log("Unknown action: ", action)
            return;
        }

        return handler(action, context);
    }

    function evaluateAll(actions, context) {
        function evaluateAction(action) {
            evaluate(action, context).then(function(result) {
                var contextCopy = JSON.parse(JSON.stringify(context));
                contextCopy.unshift(result);
                evaluateAll(action.success || [], contextCopy);
            });
        }
        actions.forEach(function(action) {
            evaluateAction(action, context);
        });
    }

    function fireAction(nameOrAction, overrideData) {
        if (typeof nameOrAction === 'object') {
            evaluateAll(nameOrAction, []);
        } else {
            var obj = actions[nameOrAction];
            if (obj !== undefined) {
                obj = JSON.parse(JSON.stringify(obj));

                (overrideData || []).forEach(function(override) {
                    var lastObj = (override.keys || []).slice(0, -1).reduce(function(state, value) {
                        if (state[value] == undefined) {
                            state[value] = {};
                        }
                        return state[value];
                    }, obj);
                    lastObj[override.keys.slice(-1)[0]] = override.value;
                });

                return evaluateAll(obj, []);
            }
        }
    }

    return {
        fire: fireAction
    }
}

function GenericApplication(selector, appData) {
    registerCoreComponents();

    var variables = appData["$variables"] || {};
    var actions = appData["$actions"] || {};
    delete appData["$variables"];
    delete appData["$actions"];

    // Setup watch object for all variables and internal 'variables'.
    var watch = {};
    Object.keys(appData).forEach(function(key) {
        watch[key] = {handler: function(val) {}, deep: true};
    });

    var app = new Vue({
        template: ALL_COMPONENTS,
        data: {
            "variables": variables,
            "data": appData["$ui"]
        },
        watch: watch,
        methods: {
            processUITemplate: function(template, item) {
                return ExpressionEvaluator(app, {context: item})(template);
            },
            evaluateCondition: function(condition) {
                var res = ConditionEvaluator(app, {})(condition);
                return res;
            },
            fireEvent: function(obj) {
                appActions.fire(obj);
            }
        }
    });
    DEBUG = app;

    // Setup actions.
    var appActions = Actions(app, actions);
    delete app["$actions"];

    appActions.fire("$load");

    // Manually mount to so that we can append it.
    app.$mount();
    $(selector).append(app.$el);

    return {
        event: function(event) {
            appActions.fire(event);
        },
        unload: function() {
            return;
        }
    };
}
<commit_msg>Use a default template of empty string if provided template is null.<commit_after>var registerComponent = function(tag, template) {
    Vue.component(tag, {
        template: template,
        props: ['data']
    });
};

var ALL_COMPONENTS =    '<weave-switch v-else-if="data.type == \'switch\'" v-bind:data="data"></weave-switch>\n' +
                        '<weave-loop v-else-if="data.type == \'loop\'" v-bind:data="data"></weave-loop>\n' +
                        '<weave-condition v-else-if="data.type == \'condition\'" v-bind:data="data"></weave-condition>\n';

var registerCoreComponents = _.once(function() {
    ALL_COMPONENTS = $("#template-all-components").html() + ALL_COMPONENTS;
    var coreComponents = [
        [
            'weave-switch',
            '
' +
            '    <all-components v-for="(value, key) in data.cases" ' +
            '                    v-if="key == $root.variables[data.variable]"' +
            '                    v-bind:data="value"></all-components>' +
            '
'
        ],
        [
            'weave-condition',
            '<all-components v-if="$root.evaluateCondition(data)"' +
            '                v-bind:data="data.value"></all-components>'
        ],
        [
            'weave-loop',
            '
' +
            '
' +
            '        <all-components v-bind:data="$root.processUITemplate(data.template, item)">' +
            '        </all-components>' +
            '
' +
            '
'
        ],
        [
            'all-components',
            ALL_COMPONENTS
        ]
    ];

    $("#template-all-components").html(ALL_COMPONENTS);
    coreComponents.forEach(function(item) {
        registerComponent.apply(null, item);
    });
});

function ExpressionEvaluator(app, contextObj, options) {
    var defaultOptions = {
        assumeIntermediateObjects: false
    }

    var curOpts = Object.assign({}, defaultOptions, options);
    contextObj.variables = app.variables;
    return function evaluate(template) {
        if (typeof template === "object" && Array.isArray(template)) {
            return template.map(function(item) {
                return evaluate(item);
            });
        } else if (typeof template === "object") {
            if (template.__vartype && template.__expression) {
                var context = contextObj[template.__vartype];
                if (context === undefined) {
                    return null;
                }

                return (template.__expression.keys || []).reduce(function(state, value) {
                    if (curOpts.assumeIntermediateObjects && state[value] == undefined) {
                        state[value] = {};
                    }
                    return state[value];
                }, context);
            }
            var result = {};
            Object.keys(template).forEach(function(key) {
                result[key] = evaluate(template[key], context);
            });
            return result;
        } else {
            return template;
        }
    };
}

function ConditionEvaluator(app, context, options) {
    var expressionEvaluator = ExpressionEvaluator(app, context, options);

    function equals(obj1, obj2) {
        return _.isEqual(expressionEvaluator(obj1), expressionEvaluator(obj2));
    }

    var comparators = {
        "eq": {fn: equals, args: 2}
    };

    return function evaluate(condition) {
        var comparatorInfo = comparators[condition.op];
        if (comparatorInfo === undefined) {
            console.log("Invalid operator: " + condition.op);
            return false;
        }
        if (comparatorInfo.args != condition.operands.length) {
            console.log("Bad number of operands")
            return false;
        }

        return comparatorInfo.fn.apply(null, condition.operands);
    };
}

function Actions(app, actions) {
    function rpc(action, context) {
        action.data.args = action.data.args || [];
        action.data.kwargs = action.data.kwargs || {};
        var data = ExpressionEvaluator(app, {result: context})(action.data);
        return $.ajax({
            url: "/api/rpc",
            type: 'post',
            contentType: 'application/json; charset=UTF-8',
            data: JSON.stringify(data)
        });
    }

    function store(data, context) {
        if (data.keys && data.keys.length) {
            var obj = (data.keys || []).slice(0, -1).reduce(function(state, value) {
                if (state[value] == undefined) {
                    state[value] = {};
                }
                return state[value];
            }, app);
            var result = ExpressionEvaluator(app, {result: context})(data.value);
            app.$set(obj, data.keys.slice(-1)[0], result);
        }
        return $.Deferred().resolve(data).promise();
    }

    function action(data, context) {
        if (data.action) {
            fireAction(data.action, data.data);
        }
        return $.Deferred().resolve(null).promise();
    }

    var handlers = {
        $rpc: rpc,
        $store: store,
        $action: action
    };

    function evaluate(action, context) {
        var handler = handlers[action.type];
        if (handler === undefined) {
            console.log("Unknown action: ", action)
            return;
        }

        return handler(action, context);
    }

    function evaluateAll(actions, context) {
        function evaluateAction(action) {
            evaluate(action, context).then(function(result) {
                var contextCopy = JSON.parse(JSON.stringify(context));
                contextCopy.unshift(result);
                evaluateAll(action.success || [], contextCopy);
            });
        }
        actions.forEach(function(action) {
            evaluateAction(action, context);
        });
    }

    function fireAction(nameOrAction, overrideData) {
        if (typeof nameOrAction === 'object') {
            evaluateAll(nameOrAction, []);
        } else {
            var obj = actions[nameOrAction];
            if (obj !== undefined) {
                obj = JSON.parse(JSON.stringify(obj));

                (overrideData || []).forEach(function(override) {
                    var lastObj = (override.keys || []).slice(0, -1).reduce(function(state, value) {
                        if (state[value] == undefined) {
                            state[value] = {};
                        }
                        return state[value];
                    }, obj);
                    lastObj[override.keys.slice(-1)[0]] = override.value;
                });

                return evaluateAll(obj, []);
            }
        }
    }

    return {
        fire: fireAction
    }
}

function GenericApplication(selector, appData) {
    registerCoreComponents();

    var variables = appData["$variables"] || {};
    var actions = appData["$actions"] || {};
    delete appData["$variables"];
    delete appData["$actions"];

    // Setup watch object for all variables and internal 'variables'.
    var watch = {};
    Object.keys(appData).forEach(function(key) {
        watch[key] = {handler: function(val) {}, deep: true};
    });

    var app = new Vue({
        template: ALL_COMPONENTS,
        data: {
            "variables": variables,
            "data": appData["$ui"]
        },
        watch: watch,
        methods: {
            processUITemplate: function(template, item) {
                return ExpressionEvaluator(app, {context: item})(template || "");
            },
            evaluateCondition: function(condition) {
                var res = ConditionEvaluator(app, {})(condition);
                return res;
            },
            fireEvent: function(obj) {
                appActions.fire(obj);
            }
        }
    });
    DEBUG = app;

    // Setup actions.
    var appActions = Actions(app, actions);
    delete app["$actions"];

    appActions.fire("$load");

    // Manually mount to so that we can append it.
    app.$mount();
    $(selector).append(app.$el);

    return {
        event: function(event) {
            appActions.fire(event);
        },
        unload: function() {
            return;
        }
    };
}
<|endoftext|>


<commit_before>/* global $, MAKE, twemoji, $$ */

import 'gsap/src/uncompressed/utils/Draggable';
import 'gsap/src/uncompressed/TweenLite';
import 'gsap/src/uncompressed/easing/EasePack';

import customSelect from './lib/custom-select';
import selectablePopup from './lib/selectable-popup';
import addScript from './lib/add-script';
import * as settings from './lib/settings';
import touchInit from './lib/touch';
import swPromise from './lib/sw.js';

Promise.all([
	addScript('https://cdn.rawgit.com/AdaRoseEdwards/dirty-dom/v1.2.2/build/dirty-dom-lib.min.js').promise,
	addScript('https://twemoji.maxcdn.com/2/twemoji.min.js').promise
]).then(() => {

	let cursorPos = 0;
	const textInput = $('#emoji__text-input');
	const message = [];
	function setChar(str) {
		if (!str) return;
		message[cursorPos] = str + skinToneSelector.dataset.value;
		textInput.innerHTML = message.map(m => combineEmojis(m)).join('');
		cursorPos = Math.min((cursorPos + 1), 2);
	}

	const skinTone = ['', '🏼', '🏿', '🏽', '🏾', '🏻'];
	const skinToneSelector = $('
');
	for (const s of skinTone) {
		const selected = s === settings.getItem('skinTone');
		skinToneSelector.appendChild($(`<li data-value='${s}'${selected ? ' data-selected="true"' : ''}>👋${s}</li>`));
	}
	$('#emoji__skin-tone-selector__wrapper').prependChild(skinToneSelector);
	customSelect(skinToneSelector, $('#emoji__options'));
	twemoji.parse(skinToneSelector);

	skinToneSelector.on('change', function (e) {
		const modifier = e.detail.value;
		settings.setItem('skinTone', modifier);
		$$('.emoji__grid-item').forEach(el => {
			el.$('.emoji__emoji').innerHTML = combineEmojis(el.dataset.emoji, modifier);
		});
	});

	function combineEmojis(emoji, skinTone='') {
		const div = MAKE.div();
		div.appendChild(MAKE.html(twemoji.parse(emoji + skinTone)).firstChild);
		return div.innerHTML;
	}

	const subEmojis = [
		['😋', '😎', '😍', '😘', '😗', '😙', '😚', '☺', '😇'],
		['✍', '✋', '✌', '👐', '👌', '👊', '👏', '👋', '🙋'],
		['👩', '👢', '👵', '👚', '👒', '👡', '👯', '🚺', '👭'],
		['👜', '🚹', '👘', '👖', '👞', '👠', '👢', '👕', '👚', '👒'],
		['😀', '😁', '😂', '😃', '😄', '😅', '😆', '😉', '😊'],
		['😐', '😑', '😶', '😣', '😥', '😮', '😪', '😫', '😴'],
		['😌', '🤓', '😛', '😜', '😝', '☹', '🙁', '😒', '😲'],
		['🖖', '😂', '💩', '😘', '🖕', '🤘', '👊', '🍖', '✌'],
		['👅', '🍑', '💋', '🏩', '💩', '👉', '👌', '🍆', '💦']
	];

	// main emojis is made out of middle emoji
	const mainEmojis = subEmojis.map(e => e[4]);

	const makeGrid = (emojis) => $(`

		${combineEmojis(emojis[0], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[1], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[2], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[3], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[4], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[5], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[6], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[7], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[8], skinToneSelector.dataset.value)}

`);

	const mainGrid = makeGrid(mainEmojis);
	mainGrid.$$('.emoji__grid-item')
	.map(function (item, i) {

		const subGrid = makeGrid(subEmojis[i]);
		subGrid.dataset.emoji = subEmojis[i][4];
		subGrid.on('emojiSelect', e => setChar(e.detail.emoji));
		item.appendChild(subGrid);
		return subGrid;
	})
	.forEach(selectablePopup);

	$('#emoji__grid').appendChild(mainGrid);
	twemoji.parse(mainGrid);
	twemoji.parse($('#emoji__options-button'));

	// Add button interactions
	touchInit();

	function sendSubscriptionToServer(subscription) {

		// make fetch request with cookies to get user id.
		fetch(`/api/subscribe?sub=${subscription}`, {
		  credentials: 'same-origin'
		}).then(e => console.log(e))
	}

	const pushButton = $('#emoji__push');
	if (pushButton) {
		pushButton.style.display = 'none';
		pushButton.on('click', subscribe);
	}
	function subscribe() {
		swPromise.then(serviceWorkerRegistration => {
			serviceWorkerRegistration.pushManager.subscribe()
			.then(function(subscription) {
				pushButton.style.display = 'none';
				return sendSubscriptionToServer(subscription);
			})
			.catch(function(e) {
				if (Notification.permission === 'denied') {
					pushButton.style.display = '';
					console.warn('Permission for Notifications was denied');
				} else {
					// A problem occurred with the subscription; common reasons
					// include network errors, and lacking gcm_sender_id and/or
					// gcm_user_visible_only in the manifest.
					console.error('Unable to subscribe to push.', e);
				}
			});
		});
	}

	swPromise
	.then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.getSubscription())
	.then(subscription => {
		if (!subscription) {

			if (pushButton) {
				// Not subscribed: show subscribe button
				pushButton.style.display = '';
			}
		} else {

			// Update server with correct info.
			return sendSubscriptionToServer(subscription);
		}
	})
	.catch(e => {

		// Service workers not supported.
		console.log('service workers/push notifications not supported.')
		console.log(e);
	});
})
.catch(e => {

	// Script loading errors
	console.log(e);
});
<commit_msg>visible notificatiosn only<commit_after>/* global $, MAKE, twemoji, $$ */

import 'gsap/src/uncompressed/utils/Draggable';
import 'gsap/src/uncompressed/TweenLite';
import 'gsap/src/uncompressed/easing/EasePack';

import customSelect from './lib/custom-select';
import selectablePopup from './lib/selectable-popup';
import addScript from './lib/add-script';
import * as settings from './lib/settings';
import touchInit from './lib/touch';
import swPromise from './lib/sw.js';

Promise.all([
	addScript('https://cdn.rawgit.com/AdaRoseEdwards/dirty-dom/v1.2.2/build/dirty-dom-lib.min.js').promise,
	addScript('https://twemoji.maxcdn.com/2/twemoji.min.js').promise
]).then(() => {

	let cursorPos = 0;
	const textInput = $('#emoji__text-input');
	const message = [];
	function setChar(str) {
		if (!str) return;
		message[cursorPos] = str + skinToneSelector.dataset.value;
		textInput.innerHTML = message.map(m => combineEmojis(m)).join('');
		cursorPos = Math.min((cursorPos + 1), 2);
	}

	const skinTone = ['', '🏼', '🏿', '🏽', '🏾', '🏻'];
	const skinToneSelector = $('
');
	for (const s of skinTone) {
		const selected = s === settings.getItem('skinTone');
		skinToneSelector.appendChild($(`<li data-value='${s}'${selected ? ' data-selected="true"' : ''}>👋${s}</li>`));
	}
	$('#emoji__skin-tone-selector__wrapper').prependChild(skinToneSelector);
	customSelect(skinToneSelector, $('#emoji__options'));
	twemoji.parse(skinToneSelector);

	skinToneSelector.on('change', function (e) {
		const modifier = e.detail.value;
		settings.setItem('skinTone', modifier);
		$$('.emoji__grid-item').forEach(el => {
			el.$('.emoji__emoji').innerHTML = combineEmojis(el.dataset.emoji, modifier);
		});
	});

	function combineEmojis(emoji, skinTone='') {
		const div = MAKE.div();
		div.appendChild(MAKE.html(twemoji.parse(emoji + skinTone)).firstChild);
		return div.innerHTML;
	}

	const subEmojis = [
		['😋', '😎', '😍', '😘', '😗', '😙', '😚', '☺', '😇'],
		['✍', '✋', '✌', '👐', '👌', '👊', '👏', '👋', '🙋'],
		['👩', '👢', '👵', '👚', '👒', '👡', '👯', '🚺', '👭'],
		['👜', '🚹', '👘', '👖', '👞', '👠', '👢', '👕', '👚', '👒'],
		['😀', '😁', '😂', '😃', '😄', '😅', '😆', '😉', '😊'],
		['😐', '😑', '😶', '😣', '😥', '😮', '😪', '😫', '😴'],
		['😌', '🤓', '😛', '😜', '😝', '☹', '🙁', '😒', '😲'],
		['🖖', '😂', '💩', '😘', '🖕', '🤘', '👊', '🍖', '✌'],
		['👅', '🍑', '💋', '🏩', '💩', '👉', '👌', '🍆', '💦']
	];

	// main emojis is made out of middle emoji
	const mainEmojis = subEmojis.map(e => e[4]);

	const makeGrid = (emojis) => $(`

		${combineEmojis(emojis[0], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[1], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[2], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[3], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[4], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[5], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[6], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[7], skinToneSelector.dataset.value)}
		${combineEmojis(emojis[8], skinToneSelector.dataset.value)}

`);

	const mainGrid = makeGrid(mainEmojis);
	mainGrid.$$('.emoji__grid-item')
	.map(function (item, i) {

		const subGrid = makeGrid(subEmojis[i]);
		subGrid.dataset.emoji = subEmojis[i][4];
		subGrid.on('emojiSelect', e => setChar(e.detail.emoji));
		item.appendChild(subGrid);
		return subGrid;
	})
	.forEach(selectablePopup);

	$('#emoji__grid').appendChild(mainGrid);
	twemoji.parse(mainGrid);
	twemoji.parse($('#emoji__options-button'));

	// Add button interactions
	touchInit();

	function sendSubscriptionToServer(subscription) {

		// make fetch request with cookies to get user id.
		fetch(`/api/subscribe?sub=${subscription}`, {
		  credentials: 'same-origin'
		}).then(e => console.log(e))
	}

	const pushButton = $('#emoji__push');
	if (pushButton) {
		pushButton.style.display = 'none';
		pushButton.on('click', subscribe);
	}
	function subscribe() {
		swPromise.then(serviceWorkerRegistration => {
			serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
			.then(function(subscription) {
				pushButton.style.display = 'none';
				return sendSubscriptionToServer(subscription);
			})
			.catch(function(e) {
				if (Notification.permission === 'denied') {
					pushButton.style.display = '';
					console.warn('Permission for Notifications was denied');
				} else {
					// A problem occurred with the subscription; common reasons
					// include network errors, and lacking gcm_sender_id and/or
					// gcm_user_visible_only in the manifest.
					console.error('Unable to subscribe to push.', e);
				}
			});
		});
	}

	swPromise
	.then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.getSubscription())
	.then(subscription => {
		if (!subscription) {

			if (pushButton) {
				// Not subscribed: show subscribe button
				pushButton.style.display = '';
			}
		} else {

			// Update server with correct info.
			return sendSubscriptionToServer(subscription);
		}
	})
	.catch(e => {

		// Service workers not supported.
		console.log('service workers/push notifications not supported.')
		console.log(e);
	});
})
.catch(e => {

	// Script loading errors
	console.log(e);
});
<|endoftext|>


<commit_before>/**
 * In this file, we create a React component
 * which incorporates components provided by Material-UI.
 */
import React, {Component} from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import Dialog from 'material-ui/Dialog';
import {deepOrange500} from 'material-ui/styles/colors';
import FlatButton from 'material-ui/FlatButton';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import AppBar from 'material-ui/AppBar';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
import Login from './login/login.jsx';
import * as loginCtrl from './login/loginCtrl';
import { Link } from 'react-router'

const styles = {
  container: {
    textAlign: 'center',
    paddingTop: 200,
  },
};

const muiTheme = getMuiTheme({
  palette: {
    accent1Color: deepOrange500,
  },
});

class Main extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      isLoggedIn: !!loginCtrl.getJwt(),
      open: false
    };
  }

  onProfileClick(){
    console.log('clicked');
  }

  handleLogout() {
    loginCtrl.logout();
    this.setState({
      isLoggedIn: false
    });
  }

  handleDrawerToggle = () => this.setState({open: !this.state.open});
  handleClose = () => this.setState({open: false});

  render() {
    const logOutButton = this.state.isLoggedIn ?
      (<FlatButton label="Logout"
        onTouchTap={this.handleLogout.bind(this)}
       />
      ) :
      null;
    const LoginModal = !this.state.isLoggedIn ?
      (<Login main={this} />) :
      null;
    return (
      <MuiThemeProvider muiTheme={muiTheme}>

          <AppBar
            title='Boopually'
            onLeftIconButtonTouchTap={this.handleDrawerToggle}
            iconElementRight={logOutButton}
          />
          <Drawer
            docked={false}
            open={this.state.open}
            onRequestChange={(open) => this.setState({open})}
          >
            <MenuItem>
              <Link to="/displayProfile">Profile</Link>
            </MenuItem>
            <MenuItem>More Stuff</MenuItem>
          </Drawer>
          {LoginModal}
          {this.props.children}

      </MuiThemeProvider>
    );
  }
}

export default Main;
<commit_msg>updated the profile link to close after clicked<commit_after>/**
 * In this file, we create a React component
 * which incorporates components provided by Material-UI.
 */
import React, {Component} from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import Dialog from 'material-ui/Dialog';
import {deepOrange500} from 'material-ui/styles/colors';
import FlatButton from 'material-ui/FlatButton';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import AppBar from 'material-ui/AppBar';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
import Login from './login/login.jsx';
import * as loginCtrl from './login/loginCtrl';
import { Link } from 'react-router'

const styles = {
  container: {
    textAlign: 'center',
    paddingTop: 200,
  },
};

const muiTheme = getMuiTheme({
  palette: {
    accent1Color: deepOrange500,
  },
});

class Main extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      isLoggedIn: !!loginCtrl.getJwt(),
      open: false
    };
  }

  handleLogout() {
    loginCtrl.logout();
    this.setState({
      isLoggedIn: false
    });
  }

  handleDrawerToggle = () => this.setState({open: !this.state.open});
  handleClose = () => this.setState({open: false});

  render() {
    const logOutButton = this.state.isLoggedIn ?
      (<FlatButton label="Logout"
        onTouchTap={this.handleLogout.bind(this)}
       />
      ) :
      null;
    const LoginModal = !this.state.isLoggedIn ?
      (<Login main={this} />) :
      null;
    return (
      <MuiThemeProvider muiTheme={muiTheme}>

          <AppBar
            title='Boopually'
            onLeftIconButtonTouchTap={this.handleDrawerToggle}
            iconElementRight={logOutButton}
          />
          <Drawer
            docked={false}
            open={this.state.open}
            onRequestChange={(open) => this.setState({open})}
          >
            <MenuItem>
              <Link to="/displayProfile" onClick={this.handleDrawerToggle}>Profile</Link>
            </MenuItem>
            <MenuItem>More Stuff</MenuItem>
          </Drawer>
          {LoginModal}
          {this.props.children}

      </MuiThemeProvider>
    );
  }
}

export default Main;
<|endoftext|>


<commit_before>/**
 * Copyright 2013 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var url = require('url'),
    assert = require('assert'),
    qs = require('querystring'),
    fs = require('fs');

var googleapis = require('../lib/googleapis.js'),
    MockTransporter = require('./mocks/transporters.js');

describe('Clients', function() {

  var mockTransporter =
        new MockTransporter(__dirname + '/data/discovery_plus.json');

  it('should create request helpers according ' +
        'to the resource on discovery API response', function(done) {
    new googleapis.GoogleApis()
        .discover('plus', 'v1')
        .execute(function(err, client) {
      assert.equal(typeof client.plus.people.get, 'function');
      assert.equal(typeof client.plus.activities.search, 'function');
      assert.equal(typeof client.plus.comments.list, 'function');
      done();
    });
  });

  it('should be able to generate default discovery url', function(done) {
    var api = { name: 'plus', version: 'v3', opts: {} };
    var discoveryUrl =
        new googleapis.GoogleApis().generateDiscoveryUrl(api);
    var parsed = url.parse(discoveryUrl);
    assert.equal(parsed.protocol, 'https:');
    assert.equal(parsed.host, 'www.googleapis.com');
    assert.equal(parsed.path, '/discovery/v1/apis/plus/v3/rest');
    assert.equal(parsed.query, null);
    done();
  });

  it('should be able to generate default discovery url with custom ' +
      'base url and parameters configuration', function(done) {
    var api = { name: 'plus', version: 'v3', opts: {
      baseDiscoveryUrl: 'http://mydeployment/discovery/',
      discoveryParams: { a: 'hello', b: 'hi' }
    }};
    var discoveryUrl = new googleapis.GoogleApis().generateDiscoveryUrl(api);
    var parsed = url.parse(discoveryUrl);
    assert.equal(parsed.protocol, 'http:');
    assert.equal(parsed.host, 'mydeployment');
    assert.equal(parsed.pathname, '/discovery/plus/v3/rest');
    assert.equal(parsed.query, 'a=hello&b=hi');
    done();
  });

  it('should be able to add AuthClient on new requests', function(done) {
    new googleapis.GoogleApis()
      .discover('plus', 'v1')
      .execute(function(err, client) {
        var req =
            client.plus.withAuthClient({ credentials: 'dummy'}).newRequest();
        assert.equal('dummy', req.authClient.credentials);
        done();
      });
  });

  it('should be able to add defaultParams on new requests', function(done) {
    new googleapis.GoogleApis()
      .discover('plus', 'v1')
      .execute(function(err, client) {
        var req =
            client.plus.withDefaultParams({a: 1, b: 'foo'}).newRequest('doIt', {a: 2});
        assert.equal(2, req.params.a);
        assert.equal('foo', req.params.b);
        done();
      });
  });

});
<commit_msg>testsclient: fix test for defaultParams<commit_after>/**
 * Copyright 2013 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var url = require('url'),
    assert = require('assert'),
    qs = require('querystring'),
    fs = require('fs');

var googleapis = require('../lib/googleapis.js'),
    MockTransporter = require('./mocks/transporters.js');

describe('Clients', function() {

  var mockTransporter =
        new MockTransporter(__dirname + '/data/discovery_plus.json');

  it('should create request helpers according ' +
        'to the resource on discovery API response', function(done) {
    new googleapis.GoogleApis()
        .discover('plus', 'v1')
        .execute(function(err, client) {
      assert.equal(typeof client.plus.people.get, 'function');
      assert.equal(typeof client.plus.activities.search, 'function');
      assert.equal(typeof client.plus.comments.list, 'function');
      done();
    });
  });

  it('should be able to generate default discovery url', function(done) {
    var api = { name: 'plus', version: 'v3', opts: {} };
    var discoveryUrl =
        new googleapis.GoogleApis().generateDiscoveryUrl(api);
    var parsed = url.parse(discoveryUrl);
    assert.equal(parsed.protocol, 'https:');
    assert.equal(parsed.host, 'www.googleapis.com');
    assert.equal(parsed.path, '/discovery/v1/apis/plus/v3/rest');
    assert.equal(parsed.query, null);
    done();
  });

  it('should be able to generate default discovery url with custom ' +
      'base url and parameters configuration', function(done) {
    var api = { name: 'plus', version: 'v3', opts: {
      baseDiscoveryUrl: 'http://mydeployment/discovery/',
      discoveryParams: { a: 'hello', b: 'hi' }
    }};
    var discoveryUrl = new googleapis.GoogleApis().generateDiscoveryUrl(api);
    var parsed = url.parse(discoveryUrl);
    assert.equal(parsed.protocol, 'http:');
    assert.equal(parsed.host, 'mydeployment');
    assert.equal(parsed.pathname, '/discovery/plus/v3/rest');
    assert.equal(parsed.query, 'a=hello&b=hi');
    done();
  });

  it('should be able to add AuthClient on new requests', function(done) {
    new googleapis.GoogleApis()
      .discover('plus', 'v1')
      .execute(function(err, client) {
        var req =
            client.plus.withAuthClient({ credentials: 'dummy'}).newRequest();
        assert.equal('dummy', req.authClient.credentials);
        done();
      });
  });

  it('should be able to add defaultParams on new requests', function(done) {
    new googleapis.GoogleApis()
      .discover('plus', 'v1')
      .execute(function(err, client) {
        var req =
            client.plus.withDefaultParams({a: 1, b: 'foo'}).newRequest('doIt', {a: 2}, {});
        assert.equal(2, req.params.a);
        assert.equal('foo', req.params.b);
        done();
      });
  });

});
<|endoftext|>


<commit_before>{
        'type': 'game', // game or console
        'name': 'Tennis For Two',
        'authors': ['William Higinbotham'],
        'company': ['Brookhaven National Laboratory'],
        'year': '1958,
        'date': '18-10-1958', // Day, Month, Year
        'headline': 'Um dos primeiros jogos eletrônicos a usar um display gráfico',
        'resume': [
            'Tennis for Two é frequentemente considerado como um dos ',
            'primeiros jogos já criados. Desenvolvido por William ',
            'Higginbotham, um físico que trabalhou no Projeto Manhattan, ',
            'Tennis for Two foi completado em 18 de Outubro de 1958, ',
            'muito antes de os primeiros jogos serem comercializados.',
        ].join(''),
        'full_text': [
            'Tennis for Two é  considerado um dos primeiros jogos, ',
            'já lançados, se não o primeiro. Está entre os primeiros ',
            'jogos que contou com um display gráfico. Higginbotham ',
            'criou o jogo em resposta ao "Dia de Exposições" no ',
            'Laboratório Nacional de Brookhaven em Upton, Nova Iorque. ',
            'De acordo com Higinbotham, o dia de exposições sempre era ',
            'um tanto monótomo e entediante, e Tennis for Two foi criado ',
            'em resposta a esse fato, para tornar o evento mais ',
            'emocionante. Um dos computadores do exército era capaz de ',
            'calcular a trajetória de mísseis balísticos, e ele usou ',
            'essa tecnologia para desenvolver o jogo. Sem surpresa, o jogo ',
            'foi um enorme sucesso entre os participantes e relatos sugerem ',
            'que enormes filas foram formadas para jogar o \'simples\', mas, ',
            'no momento, \'inovador\' jogo. Mesmo que Higginbotham pecebesse que ',
            'tinha desenvolvido algo original, ele nunca patenteou a ',
            'ideia devido ao fato de que o governo dos Estados Unidos ',
            'teria tomado posse dela("Mas se eu tivesse percebido o ',
            'quão significativo havia sido, eu teria patenteado ',
            'e seria posse do governo dos EUA!" -Higginbotham). ',
            'Durante dois anos, o jogo foi desenvolvido, até que fosse ',
            'infelizmente, descontinuado.'
        ].join(''),
        'thumbnail': 'thumbnail_example.png',
        'main_img': {'name': 'main_picture.png', 'caption': "description"},
        'images': [
            {'name': 'picture1.png', 'caption': "description1"},
            {'name': 'picture2.png', 'caption': "description2"},
            {'name': 'picture3.png', 'caption': "description3"}
        ],
        'video_gameplay': [
            {'url': 'https://www.youtube.com/watch?v=s2E9iSQfGdg', 'caption': "Tennis For Two - The second ever computer game"} // type: youtube or vimeo
        ],
        'demos': [
            {'url': 'demonstration_url', 'caption': "description"}
        ],
        'references': [
            {'url': 'http://videogamehistory.wikia.com/wiki/Tennis_for_Two', 'caption': "Video Game History Wiki"},
            {'url': 'http://www.youtube.com/watch?v=6QSHZ20MQfE', 'caption': "When Games Went Click: The Story of Tennis for Two"},
            {'url': 'http://www.bnl.gov/bnlweb/history/higinbotham.asp', 'caption': "When Games Went Click: The Story of Tennis for Two"}
        ]
}
<commit_msg>Update tennisfortwopt_temp.js<commit_after>{
        'type': 'game', // game or console
        'name': 'Tennis For Two',
        'authors': ['William Higinbotham'],
        'company': ['Brookhaven National Laboratory'],
        'year': '1958,
        'date': '18-10-1958', // Day, Month, Year
        'headline': 'Um dos primeiros jogos eletrônicos a usar um display gráfico',
        'resume': [
            'Tennis for Two é frequentemente considerado como um dos ',
            'primeiros jogos já criados. Desenvolvido por William ',
            'Higginbotham, um físico que trabalhou no Projeto Manhattan, ',
            'Tennis for Two foi completado em 18 de Outubro de 1958, ',
            'muito antes de os primeiros jogos serem comercializados.',
        ].join(''),
        'full_text': [
            'Tennis for Two é  considerado um dos primeiros jogos, ',
            'já lançados, se não o primeiro. Está entre os primeiros ',
            'jogos que contou com um display gráfico. Higginbotham ',
            'criou o jogo em resposta ao "Dia de Exposições" no ',
            'Laboratório Nacional de Brookhaven em Upton, Nova Iorque. ',
            'De acordo com Higinbotham, o dia de exposições sempre era ',
            'um tanto monótomo e entediante, e Tennis for Two foi criado ',
            'em resposta a esse fato, para tornar o evento mais ',
            'emocionante. Um dos computadores do exército era capaz de ',
            'calcular a trajetória de mísseis balísticos, e ele usou ',
            'essa tecnologia para desenvolver o jogo. Sem surpresa, o jogo ',
            'foi um enorme sucesso entre os participantes e relatos sugerem ',
            'que enormes filas foram formadas para jogar o simples, mas, ',
            'no momento, inovador jogo. Mesmo que Higginbotham tivesse ',
            'percebido que tinha desenvolvido algo original, ele nunca patenteou ',
            'a ideia devido ao fato de que o governo dos Estados Unidos ',
            'teria tomado posse dela("Mas se eu tivesse percebido o ',
            'quão significativo havia sido, eu teria patenteado ',
            'e seria posse do governo dos EUA!" -Higginbotham). ',
            'Durante dois anos, o jogo foi desenvolvido, até que fosse ',
            'infelizmente, descontinuado.'
        ].join(''),
        'thumbnail': 'thumbnail_example.png',
        'main_img': {'name': 'main_picture.png', 'caption': "description"},
        'images': [
            {'name': 'picture1.png', 'caption': "description1"},
            {'name': 'picture2.png', 'caption': "description2"},
            {'name': 'picture3.png', 'caption': "description3"}
        ],
        'video_gameplay': [
            {'url': 'https://www.youtube.com/watch?v=s2E9iSQfGdg', 'caption': "Tennis For Two - The second ever computer game"} // type: youtube or vimeo
        ],
        'demos': [
            {'url': 'demonstration_url', 'caption': "description"}
        ],
        'references': [
            {'url': 'http://videogamehistory.wikia.com/wiki/Tennis_for_Two', 'caption': "Video Game History Wiki"},
            {'url': 'http://www.youtube.com/watch?v=6QSHZ20MQfE', 'caption': "When Games Went Click: The Story of Tennis for Two"},
            {'url': 'http://www.bnl.gov/bnlweb/history/higinbotham.asp', 'caption': "When Games Went Click: The Story of Tennis for Two"}
        ]
}
<|endoftext|>


<commit_before>import { useEffect } from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';
import produce from 'immer';

const LIST_ARTICLE_QUERY = gql`
  query Articles {
    ListArticles(orderBy: [{ createdAt: DESC }]) {
      edges {
        node {
          id
          text
        }
        cursor
      }
      pageInfo {
        firstCursor
        lastCursor
      }
    }
  }
`;

const LIST_ARTICLE_ON_LOAD = gql`
  query ArticleUsers {
    ListArticles(orderBy: [{ createdAt: DESC }]) {
      edges {
        node {
          user {
            id
            name
          }
        }
      }
    }
  }
`;

const updateArticlesOnLoad = produce((articlesQuery, articleUsersQuery) => {
  const articleEdges = articleUsersQuery.ListArticles.edges;
  articlesQuery.ListArticles.edges.forEach((edge, idx) => {
    edge.node = { ...edge.node, ...articleEdges[idx].node };
  });
});

function ArticleList() {
  const { loading, data, fetchMore } = useQuery(LIST_ARTICLE_QUERY);

  useEffect(() => {
    fetchMore({
      query: LIST_ARTICLE_ON_LOAD,
      updateQuery(prev, { fetchMoreResult }) {
        if (!fetchMoreResult) return prev;
        const updated = updateArticlesOnLoad(prev, fetchMoreResult);
        return updated;
      },
    });
  }, []);

  return loading ? (

Loading

  ) : (

{JSON.stringify(data, null, '  ')}

  );
}

export default ArticleList;
<commit_msg>Remove redundant file ArticleList.js<commit_after><|endoftext|>


<commit_before>'use strict';

(function (angular, buildfire) {
  angular.module('contactUsPluginWidget')
    .controller('WidgetHomeCtrl', ['$routeParams', 'Buildfire', 'DataStore', '$scope', 'TAG_NAMES', 'Location', 'LAYOUTS', '$rootScope', '$sce',
      function ($routeParams, Buildfire, DataStore, $scope, TAG_NAMES, Location, LAYOUTS, $rootScope, $sce) {
        var WidgetHome = this;
        var currentListLayout = null;
        WidgetHome.data = {};

        //create new instance of buildfire carousel viewer
        WidgetHome.view = null;

        //Refresh data on pulling the tile bar

        buildfire.datastore.onRefresh(function () {
          init(function (err) {
            if (!err) {
              if (!WidgetHome.view) {
                WidgetHome.view = new Buildfire.components.carousel.view("#carousel", []);
              }
              if (WidgetHome.data.content && WidgetHome.data.content.carouselImages) {
                WidgetHome.view.loadItems(WidgetHome.data.content.carouselImages);
              } else {
                WidgetHome.view.loadItems([]);
              }
            }
          });
        });

        var _dummyData = {
          content: {
            showMap: true,
            carouselImages: [{
              action: "noAction",
              iconUrl: "http://buildfire.imgix.net/1462345835888-04866688400506973/6ac49110-11c7-11e6-92ea-27ed66023d52.jpeg?fit=crop&w=342&h=193",
              title: "image"
            },
              {
                action: "noAction",
                iconUrl: "http://buildfire.imgix.net/1462345835888-04866688400506973/6bf3c240-11c7-11e6-ad08-375cc71b6ca7.jpg?fit=crop&w=342&h=193",
                title: "image"
              }],
            description: "
With the wysiwyg, you can include text and lists, embed images, embed videos, and link to webpages, emails, phone numbers and more. Check out the tutorial on the wysiwyg for detailed information.
",
            addressTitle: "",
            address: {
              type: "Location",
              location: "501 Pacific Hwy, San Diego, CA 92101, USA",
              location_coordinates: [-117.17096400000003, 32.7100444]
            },
            links: [{
              "title": "Call",
              "action": "callNumber",
              "phoneNumber": "6195551234"
            }, {"title": "Email", "action": "sendEmail"}]
          },
          design: {
            listLayout: "Layout_1",
            backgroundImage: ""
          }
        };

        /*declare the device width heights*/
        $rootScope.deviceHeight = window.innerHeight;
        $rootScope.deviceWidth = window.innerWidth || 320;
        $rootScope.backgroundImage = "";
        WidgetHome.device = null;
        /*initialize the device width heights*/
        function initDeviceSize(callback) {
          WidgetHome.deviceHeight = window.innerHeight;
          WidgetHome.deviceWidth = window.innerWidth;
          if (callback) {
            if (WidgetHome.deviceWidth == 0 || WidgetHome.deviceHeight == 0) {
              setTimeout(function () {
                initDeviceSize(callback);
              }, 500);
            } else {
              callback();
              if (!$scope.$$phase && !$scope.$root.$$phase) {
                $scope.$apply();
              }
            }
          }
        }

        /*crop image on the basis of width heights*/
        WidgetHome.cropImage = function (url, settings) {
          var options = {};
          if (!url) {
            return "";
          }
          else {
            if (settings.height) {
              options.height = settings.height;
            }
            if (settings.width) {
              options.width = settings.width;
            }
            return Buildfire.imageLib.cropImage(url, options);
          }
        };

        /*
         * Fetch user's data from datastore
         */
        var init = function (cb) {
          var success = function (result) {
              if (!result.id) {
                console.log('NO DATA AVAILABLE');
                WidgetHome.data = _dummyData;
              } else {
                WidgetHome.data = result.data;
              }
              if (!WidgetHome.data.design) {
                WidgetHome.data.design = {};
              }
              if (!WidgetHome.data.design.listLayout) {
                WidgetHome.data.design.listLayout = LAYOUTS.listLayouts[0].name;
              }
              currentListLayout = WidgetHome.data.design.listLayout;
              if (WidgetHome.data.design.backgroundImage) {
                $rootScope.backgroundImage = WidgetHome.data.design.backgroundImage;
              }
              else {
                $rootScope.backgroundImage = "";
              }
              var getDevice = function (error, data) {
                if (data)
                  WidgetHome.device = data.device;
                else
                  console.log("Error while getting the device context data", error)
              };
              buildfire.getContext(getDevice);
              cb();

            }
            , error = function (err) {
              console.error('Error while getting data', err);
              cb(err);
            };
          DataStore.get(TAG_NAMES.CONTACT_INFO).then(success, error);
        };
        init(function(){});
        $scope.$on('$viewContentLoaded', function () {
          $rootScope.$on("Carousel:LOADED", function () {
            if (!WidgetHome.view) {
              WidgetHome.view = new Buildfire.components.carousel.view("#carousel", []);
            }
            if (WidgetHome.data.content && WidgetHome.data.content.carouselImages) {
              WidgetHome.view.loadItems(WidgetHome.data.content.carouselImages);
            } else {
              WidgetHome.view.loadItems([]);
            }
          });
        });

        var onUpdateCallback = function (event) {
          setTimeout(function () {
            $scope.imagesUpdated = false;
            $scope.$digest();
            if (event && event.tag === TAG_NAMES.CONTACT_INFO) {
              WidgetHome.data = event.data;
              if (!WidgetHome.data.design)
                WidgetHome.data.design = {};
              if (!WidgetHome.data.content)
                WidgetHome.data.content = {};
            }
            if (WidgetHome.data.design.backgroundImage) {
              $rootScope.backgroundImage = WidgetHome.data.design.backgroundImage;
            } else {
              $rootScope.backgroundImage = "";
            }
            if (!WidgetHome.data.design.listLayout) {
              WidgetHome.data.design.listLayout = LAYOUTS.listLayouts[0].name;
            }
            if (currentListLayout != WidgetHome.data.design.listLayout && WidgetHome.view && WidgetHome.data.content.carouselImages) {
              WidgetHome.view._destroySlider();
              WidgetHome.view = null;
            }
            else {
              if (WidgetHome.view) {
                WidgetHome.view.loadItems(WidgetHome.data.content.carouselImages);
              }
            }
            currentListLayout = WidgetHome.data.design.listLayout;
            $scope.imagesUpdated = !!event.data.content;
            $scope.$digest();
            $rootScope.$digest();
          }, 0);
        };
        DataStore.onUpdate().then(null, null, onUpdateCallback);

        $scope.$on("$destroy", function () {
          DataStore.clearListener();
        });
        WidgetHome.safeHtml = function (html) {
          if (html) {
            var $html = $('
', {html: html});
            $html.find('iframe').each(function (index, element) {
              var src = element.src;
              console.log('element is: ', src, src.indexOf('http'));
              src = src && src.indexOf('file://') != -1 ? src.replace('file://', 'http://') : src;
              element.src = src && src.indexOf('http') != -1 ? src : 'http:' + src;
            });
            return $sce.trustAsHtml($html.html());
          }
        };

        WidgetHome.openLinks = function (actionItems, $event) {
          if (actionItems && actionItems.length) {
            var options = {};
            var callback = function (error, result) {
              if (error) {
                console.error('Error:', error);
              }
            };
			$event.preventDefault();
			Buildfire.actionItems.list(actionItems, options, callback);
          }
        };

        WidgetHome.onAddressClick = function (long, lat) {
          if (WidgetHome.device && WidgetHome.device.platform == 'ios')
            buildfire.navigation.openWindow("maps://maps.google.com/maps?daddr=" + lat + "," + long, '_system');
          else
            buildfire.navigation.openWindow("http://maps.google.com/maps?daddr=" + lat + "," + long, '_system');
        };

        WidgetHome.executeOperation = function(item){
          buildfire.actionItems.execute(item, function (err, result) {
            if (err) {
              console.warn('Error opening slider action: ', err);
            }
          });
        }

      }])
})(window.angular, window.buildfire);
<commit_msg>add 0 timeout to make the action items list touch event complete faster<commit_after>'use strict';

(function (angular, buildfire) {
  angular.module('contactUsPluginWidget')
    .controller('WidgetHomeCtrl', ['$routeParams', 'Buildfire', 'DataStore', '$scope', 'TAG_NAMES', 'Location', 'LAYOUTS', '$rootScope', '$sce', '$timeout',
      function ($routeParams, Buildfire, DataStore, $scope, TAG_NAMES, Location, LAYOUTS, $rootScope, $sce, $timeout) {
        var WidgetHome = this;
        var currentListLayout = null;
        WidgetHome.data = {};

        //create new instance of buildfire carousel viewer
        WidgetHome.view = null;

        //Refresh data on pulling the tile bar

        buildfire.datastore.onRefresh(function () {
          init(function (err) {
            if (!err) {
              if (!WidgetHome.view) {
                WidgetHome.view = new Buildfire.components.carousel.view("#carousel", []);
              }
              if (WidgetHome.data.content && WidgetHome.data.content.carouselImages) {
                WidgetHome.view.loadItems(WidgetHome.data.content.carouselImages);
              } else {
                WidgetHome.view.loadItems([]);
              }
            }
          });
        });

        var _dummyData = {
          content: {
            showMap: true,
            carouselImages: [{
              action: "noAction",
              iconUrl: "http://buildfire.imgix.net/1462345835888-04866688400506973/6ac49110-11c7-11e6-92ea-27ed66023d52.jpeg?fit=crop&w=342&h=193",
              title: "image"
            },
              {
                action: "noAction",
                iconUrl: "http://buildfire.imgix.net/1462345835888-04866688400506973/6bf3c240-11c7-11e6-ad08-375cc71b6ca7.jpg?fit=crop&w=342&h=193",
                title: "image"
              }],
            description: "
With the wysiwyg, you can include text and lists, embed images, embed videos, and link to webpages, emails, phone numbers and more. Check out the tutorial on the wysiwyg for detailed information.
",
            addressTitle: "",
            address: {
              type: "Location",
              location: "501 Pacific Hwy, San Diego, CA 92101, USA",
              location_coordinates: [-117.17096400000003, 32.7100444]
            },
            links: [{
              "title": "Call",
              "action": "callNumber",
              "phoneNumber": "6195551234"
            }, {"title": "Email", "action": "sendEmail"}]
          },
          design: {
            listLayout: "Layout_1",
            backgroundImage: ""
          }
        };

        /*declare the device width heights*/
        $rootScope.deviceHeight = window.innerHeight;
        $rootScope.deviceWidth = window.innerWidth || 320;
        $rootScope.backgroundImage = "";
        WidgetHome.device = null;
        /*initialize the device width heights*/
        function initDeviceSize(callback) {
          WidgetHome.deviceHeight = window.innerHeight;
          WidgetHome.deviceWidth = window.innerWidth;
          if (callback) {
            if (WidgetHome.deviceWidth == 0 || WidgetHome.deviceHeight == 0) {
              setTimeout(function () {
                initDeviceSize(callback);
              }, 500);
            } else {
              callback();
              if (!$scope.$$phase && !$scope.$root.$$phase) {
                $scope.$apply();
              }
            }
          }
        }

        /*crop image on the basis of width heights*/
        WidgetHome.cropImage = function (url, settings) {
          var options = {};
          if (!url) {
            return "";
          }
          else {
            if (settings.height) {
              options.height = settings.height;
            }
            if (settings.width) {
              options.width = settings.width;
            }
            return Buildfire.imageLib.cropImage(url, options);
          }
        };

        /*
         * Fetch user's data from datastore
         */
        var init = function (cb) {
          var success = function (result) {
              if (!result.id) {
                console.log('NO DATA AVAILABLE');
                WidgetHome.data = _dummyData;
              } else {
                WidgetHome.data = result.data;
              }
              if (!WidgetHome.data.design) {
                WidgetHome.data.design = {};
              }
              if (!WidgetHome.data.design.listLayout) {
                WidgetHome.data.design.listLayout = LAYOUTS.listLayouts[0].name;
              }
              currentListLayout = WidgetHome.data.design.listLayout;
              if (WidgetHome.data.design.backgroundImage) {
                $rootScope.backgroundImage = WidgetHome.data.design.backgroundImage;
              }
              else {
                $rootScope.backgroundImage = "";
              }
              var getDevice = function (error, data) {
                if (data)
                  WidgetHome.device = data.device;
                else
                  console.log("Error while getting the device context data", error)
              };
              buildfire.getContext(getDevice);
              cb();

            }
            , error = function (err) {
              console.error('Error while getting data', err);
              cb(err);
            };
          DataStore.get(TAG_NAMES.CONTACT_INFO).then(success, error);
        };
        init(function(){});
        $scope.$on('$viewContentLoaded', function () {
          $rootScope.$on("Carousel:LOADED", function () {
            if (!WidgetHome.view) {
              WidgetHome.view = new Buildfire.components.carousel.view("#carousel", []);
            }
            if (WidgetHome.data.content && WidgetHome.data.content.carouselImages) {
              WidgetHome.view.loadItems(WidgetHome.data.content.carouselImages);
            } else {
              WidgetHome.view.loadItems([]);
            }
          });
        });

        var onUpdateCallback = function (event) {
          setTimeout(function () {
            $scope.imagesUpdated = false;
            $scope.$digest();
            if (event && event.tag === TAG_NAMES.CONTACT_INFO) {
              WidgetHome.data = event.data;
              if (!WidgetHome.data.design)
                WidgetHome.data.design = {};
              if (!WidgetHome.data.content)
                WidgetHome.data.content = {};
            }
            if (WidgetHome.data.design.backgroundImage) {
              $rootScope.backgroundImage = WidgetHome.data.design.backgroundImage;
            } else {
              $rootScope.backgroundImage = "";
            }
            if (!WidgetHome.data.design.listLayout) {
              WidgetHome.data.design.listLayout = LAYOUTS.listLayouts[0].name;
            }
            if (currentListLayout != WidgetHome.data.design.listLayout && WidgetHome.view && WidgetHome.data.content.carouselImages) {
              WidgetHome.view._destroySlider();
              WidgetHome.view = null;
            }
            else {
              if (WidgetHome.view) {
                WidgetHome.view.loadItems(WidgetHome.data.content.carouselImages);
              }
            }
            currentListLayout = WidgetHome.data.design.listLayout;
            $scope.imagesUpdated = !!event.data.content;
            $scope.$digest();
            $rootScope.$digest();
          }, 0);
        };
        DataStore.onUpdate().then(null, null, onUpdateCallback);

        $scope.$on("$destroy", function () {
          DataStore.clearListener();
        });
        WidgetHome.safeHtml = function (html) {
          if (html) {
            var $html = $('
', {html: html});
            $html.find('iframe').each(function (index, element) {
              var src = element.src;
              console.log('element is: ', src, src.indexOf('http'));
              src = src && src.indexOf('file://') != -1 ? src.replace('file://', 'http://') : src;
              element.src = src && src.indexOf('http') != -1 ? src : 'http:' + src;
            });
            return $sce.trustAsHtml($html.html());
          }
        };

        WidgetHome.openLinks = function (actionItems, $event) {
          if (actionItems && actionItems.length) {
            var options = {};
            var callback = function (error, result) {
              if (error) {
                console.error('Error:', error);
              }
            };
            $event.preventDefault();
            $timeout(function(){
                Buildfire.actionItems.list(actionItems, options, callback);
            });
          }
        };

        WidgetHome.onAddressClick = function (long, lat) {
          if (WidgetHome.device && WidgetHome.device.platform == 'ios')
            buildfire.navigation.openWindow("maps://maps.google.com/maps?daddr=" + lat + "," + long, '_system');
          else
            buildfire.navigation.openWindow("http://maps.google.com/maps?daddr=" + lat + "," + long, '_system');
        };

        WidgetHome.executeOperation = function(item){
          buildfire.actionItems.execute(item, function (err, result) {
            if (err) {
              console.warn('Error opening slider action: ', err);
            }
          });
        }

      }])
})(window.angular, window.buildfire);
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2012 B3Partners B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * LayerSwitch component
 * Creates a LayerSwitch component
 * @author Meine Toonen
 */
Ext.define ("viewer.components.LayerSwitch",{
    extend: "viewer.components.Component",
    container: null,
    states:null,
    layers:null,
    levels:null,
    prev:null,
    appLayers:null,
    button:null,
    config: {
        top:null,
        left:null
    },
    constructor: function (conf){
        viewer.components.LayerSwitch.superclass.constructor.call(this, conf);
        this.initConfig(conf);
        this.prev = new Array();
        this.loadComponent();
        return this;
    },
    loadComponent : function (){
        var p = Ext.get(this.div).parent();
        var pid = p.id;
        this.loadLayers();

        this.button = Ext.create('Ext.button.Cycle', {
            showText: true,
            renderTo:  pid,
           // width: "100%",
            top:  '30px',
            style: {
                marginBottom: '10px'
            },
            floating: true,
            menu: {
                id: 'view-type-menu',
                items: this.states
            },
            listeners:{
                change:{
                    fn: this.layerChanged,
                    scope: this
                }
            }
        });
        this.button.zIndexManager.bringToFront(this.button);
        this.button.setPosition(Number(this.left), Number(this.top));
    },
    loadLayers : function(){
        this.layers = new Array();
        this.levels = this.viewerController.app.levels;
        this.appLayers = this.viewerController.app.appLayers;
        var backgroundRoot;
        // Find root background
        for ( var i in this.levels){
            var level = this.levels[i];
            if(level.background && !level.layers){
                backgroundRoot = level;
                break;
            }
        }
        this.states = new Array();
        this.addLevel(backgroundRoot);

    },
    addLevel : function (level){
        if(!level.background || level.layers){
            this.states.push({
                text: level.name,
                iconCls: 'view-html'
            });
        }
        if(level.layers){
            if(this.layers[level.name] == undefined ){
                this.layers[level.name] = new Array();
            }
            this.addLayers(level.layers,level)
        }
        if(level.children){
            for(var i = 0 ; i < level.children.length ;i++){
                var l = this.levels[level.children[i]];
                this.addLevel(l);
            }
        }
    },
    addLayers : function (layers, level){
        for( var i = 0 ; i < layers.length ;i++){
            var appLayerObj = this.appLayers[layers[i]];
            if(appLayerObj.checked){
                this.prev.push( appLayerObj );
            }
            this.layers[level.name].push(appLayerObj);
        }
    },
    layerChanged : function (button, active){
        if (this.viewerController.layersInitialized){
            var levelName = active.text;
            var layers = this.layers[levelName];
            this.setLayersActive(layers, true);
            if(this.prev != null){
                this.setLayersActive(this.prev,false);
            }
            this.prev = layers;
        }
    },
    setLayersActive : function (layerArray, visible){
         for( var i = 0 ; i < layerArray.length ;i++){
            var appLayerObj = layerArray[i];
            this.viewerController.setLayerVisible(appLayerObj, visible);
        }
    },
    getExtComponents: function() {
        return [this.button];
    }
});
<commit_msg>refactored LayerSwitch to work on selected content and listen to events when selected content changes and layers are made visible and invisible; now also makes sure only one background layer is visible, fixes #492<commit_after>/*
 * Copyright (C) 2012 B3Partners B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * LayerSwitch component
 *
 * Creates a Ext.button.Cycle to allow the user to quickly change the background
 * layer without the complexity of a TOC component.
 *
 * @author Meine Toonen
 */
Ext.define ("viewer.components.LayerSwitch",{
    extend: "viewer.components.Component",
    container: null,

    items: null,
    selectedBackgroundLevels: null,
    control: null,

    config: {
        top:null,
        left:null
    },

    constructor: function (conf){
        viewer.components.LayerSwitch.superclass.constructor.call(this, conf);
        this.initConfig(conf);
        this.loadComponent();

        this.viewerController.on(viewer.viewercontroller.controller.Event.ON_SELECTEDCONTENT_CHANGE, function() {
            this.loadComponent();
        }, this);

        this.viewerController.mapComponent.getMap().on(viewer.viewercontroller.controller.Event.ON_LAYER_VISIBILITY_CHANGED,
            this.layerVisibilityChanged, this);

        return this;
    },

    loadComponent : function (){
        this.loadItems();
        if(this.button) {
            this.button.destroy();
        }
        this.button = Ext.create('Ext.button.Cycle', {
            showText: true,
            renderTo:  Ext.get(this.div).parent(),
           // width: "100%",
            top:  '30px',
            style: {
                marginBottom: '10px'
            },
            floating: true,
            menu: {
                id: 'view-type-menu',
                items: this.items
            },
            listeners:{
                change:{
                    fn: this.controlItemChanged,
                    scope: this
                }
            }
        });
        this.button.zIndexManager.bringToFront(this.button);
        this.button.setPosition(Number(this.left), Number(this.top));
    },

    levelItemId: function(level) {
        return this.name + "_l" + level.id;
    },

    levelFromItemId: function(id) {
        var levelId = Number(id.substring(this.name.length+"_i".length));
        var l = null;
        Ext.each(this.selectedBackgroundLevels, function(level) {
            if(level.id == levelId) {
                l = level;
                return false;
            }
            return true;
        });
        return l;
    },

    loadItems: function() {
        var me = this;

        // Find the (singleton) level which contains all background levels
        // as children to put in the following array:
        var allBackgroundLevelIds = [];
        Ext.iterate(this.viewerController.app.levels, function(id, level) {
            if(level.background) {
                /* String array to Number array */
                allBackgroundLevelIds = Ext.Array.map(level.children, function(s) { return Number(s); });
                return false;
            }
            return true;
        });

        // Find out which background levels are in the selected content
        me.selectedBackgroundLevels = [];
        this.viewerController.traverseSelectedContent(function(level) {
            if(Ext.Array.contains(allBackgroundLevelIds, level.id)) {
                me.selectedBackgroundLevels.push(level);
            }
        }, Ext.emptyFn);

        // Create control items
        me.items = [];
        var checkedBackgroundLevel = this.getCheckedBackgroundLevel();
        Ext.each(me.selectedBackgroundLevels, function(level) {
            me.items.push({
                id: me.levelItemId(level),
                text: level.name,
                iconCls: 'view-text',
                checked: level == checkedBackgroundLevel
            });
        });
    },

    /**
     * Assume only one background level is 'checked' by checking if any of the
     * layers in the level is checked.
     */
    getCheckedBackgroundLevel: function() {
        var me = this;
        var foundLevel = null;
        Ext.each(this.selectedBackgroundLevels, function(level) {
            Ext.each(level.layers, function(appLayerId) {
                if(me.viewerController.getAppLayerById(appLayerId).checked) {
                    foundLevel = level;
                    return false;
                }
                return true;
            });
            return foundLevel == null;
        });
        return foundLevel;
    },

    controlItemChanged: function(control, item) {
        var me = this;
        // XXX either change background when layers initialized or only enable
        // control when layers initialized
        if(this.viewerController.layersInitialized) {
            var selectedLevel = me.levelFromItemId(item.id);
            Ext.each(this.selectedBackgroundLevels, function(level) {
                var checked = level == selectedLevel;
                Ext.each(level.layers, function(appLayerId) {
                    var appLayer = me.viewerController.getAppLayerById(appLayerId);
                    me.viewerController.setLayerVisible(appLayer, checked);
                });
            });
        }
    },

    layerVisibilityChanged: function(map, event) {

        var eventLevel = this.viewerController.getAppLayerParent(event.layer.id);
        var backgroundLevel = null;
        Ext.each(this.selectedBackgroundLevels, function(level) {
            if(level == eventLevel) {
                backgroundLevel = level;
                return false;
            }
            return true;
        });

        if(backgroundLevel == null) {
            return;
        }

        if(event.visible) {
            var itemId = this.levelItemId(backgroundLevel);
            var activeItem = this.button.menu.items.getByKey(itemId);
            this.button.setActiveItem(activeItem, true);
        }
    },

    getExtComponents: function() {
        return [this.button];
    }
});
<|endoftext|>


<commit_before>'use strict';

(function (angular) {
  angular.module('eventsManualPluginWidget')
    .controller('WidgetHomeCtrl', ['$scope',
      function ($scope) {

      }])
})(window.angular);
<commit_msg>Added bootstrap data in widget home controller<commit_after>'use strict';

(function (angular) {
  angular.module('eventsManualPluginWidget')
    .controller('WidgetHomeCtrl', ['$scope', 'TAG_NAMES', 'LAYOUTS', 'DataStore',
      function ($scope, TAG_NAMES, LAYOUTS, DataStore) {
        var WidgetHome = this;
        WidgetHome.data = null;

        /*
         * Fetch user's data from datastore
         */
        var init = function () {
          var success = function (result) {
              WidgetHome.data = result.data;
              if (!WidgetHome.data.content)
                WidgetHome.data.content = {};
              if (!WidgetHome.data.design)
                WidgetHome.data.design = {};
              if (!WidgetHome.data.design.itemDetailsLayout) {
                WidgetHome.data.design.itemDetailsLayout = LAYOUTS.itemDetailsLayout[0].name;
              }
            }
            , error = function (err) {
              if (err && err.code !== STATUS_CODE.NOT_FOUND) {
                console.error('Error while getting data', err);
              }
            };
          DataStore.get(TAG_NAMES.EVENTS_MANUAL_INFO).then(success, error);
        };
        init();

      }])
})(window.angular);
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';

import styles from './Panel.css';

const types = {
  info: styles.info,
  success: styles.success,
  error: styles.error
};

export default class Panel extends Component {
  render() {
    const classNames = [
      styles.root,
      types[this.props.type],
      this.props.className
    ].join(' ');

    return (

        {this.props.children}

    );
  }
}

Panel.propTypes = {
  children: PropTypes.any,
  type: PropTypes.string,
  className: PropTypes.string
};
<commit_msg>Allows Panel to accept<commit_after>import React, {Component, PropTypes} from 'react';

import styles from './Panel.css';

const types = {
  info: styles.info,
  success: styles.success,
  error: styles.error
};

export default class Panel extends Component {
  render() {
    const classNames = [
      styles.root,
      types[this.props.type],
      this.props.className
    ].join(' ');

    return (

        {this.props.children}

    );
  }
}

Panel.propTypes = {
  children: PropTypes.any,
  type: PropTypes.string,
  className: PropTypes.string
};
<|endoftext|>


<commit_before>import React, { Component, createElement } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import omit from 'lodash.omit';
import {
  IconChevronUpSmallOutline,
  IconChevronDownSmallOutline,
  IconWarningBadgedSmallFilled,
} from '@teamleader/ui-icons';
import InputMetaPropTypes from './InputMetaPropTypes';
import Box from '../box';
import Button from '../button';
import Counter from '../counter';
import { TextSmall } from '../typography';
import theme from './theme.css';

export default class Input extends Component {
  static propTypes = {
    autoFocus: PropTypes.bool,
    /** Boolean indicating whether the input text should render in bold. */
    bold: PropTypes.bool,
    /** Sets a class name to give custom styles. */
    className: PropTypes.string,
    connectedLeft: PropTypes.element,
    connectedRight: PropTypes.element,
    /** The number to render as a counter inside the input. */
    counter: PropTypes.number,
    /** Boolean indicating whether the input should render as disabled. */
    disabled: PropTypes.bool,
    /** The text string to use as help text below the input. */
    helpText: PropTypes.string,
    /** The icon displayed inside the input. */
    icon: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
    /** The position of the icon inside the input. */
    iconPlacement: PropTypes.oneOf(['left', 'right']),
    /** The text string to use as input identifier. */
    id: PropTypes.string,
    /** Boolean indicating whether the input should render as inverse. */
    inverse: PropTypes.bool,
    max: PropTypes.number,
    min: PropTypes.number,
    /** Object to provide meta information for redux forms. */
    meta: InputMetaPropTypes,
    /** Callback function that is fired when component is blurred. */
    onBlur: PropTypes.func,
    /** Callback function that is fired when the component's value changes. */
    onChange: PropTypes.func,
    /** Callback function that is fired when component is focused. */
    onFocus: PropTypes.func,
    /** The text string to use as placeholder. */
    placeholder: PropTypes.string,
    precision: PropTypes.number,
    /** Boolean indicating whether the input should render as read only. */
    readOnly: PropTypes.bool,
    /** Size of the input element. */
    size: PropTypes.oneOf(['small', 'medium', 'large']),
    /** Limit increment value for numeric inputs. */
    step: PropTypes.number,
    /** Type of the input element. It can be a valid HTML5 input type. */
    type: PropTypes.string,
    /** Current value of the input element. */
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  };

  static defaultProps = {
    iconPlacement: 'left',
    inverse: false,
    disabled: false,
    placeholder: '',
    precision: null,
    min: Number.MIN_SAFE_INTEGER,
    max: Number.MAX_SAFE_INTEGER,
    readOnly: false,
    size: 'medium',
    step: 1,
  };

  constructor(props) {
    super(props);

    this.handleChange = ::this.handleChange;
    this.handleIncreaseValue = ::this.handleIncreaseValue;
    this.handleDecreaseValue = ::this.handleDecreaseValue;

    this.state = {
      value: props.type === 'number' ? Number(props.value) || '' : props.value || '',
    };
  }

  componentWillUpdate(props, state) {
    if (props.input && props.input.onChange && state.value) {
      props.input.onChange(state.value);
    }
  }

  handleChange(event) {
    const value = event.target.value;

    this.setState(() => ({
      value: this.props.type === 'number' ? Number(value) : value,
    }));
  }

  handleIncreaseValue() {
    this.updateStep(1);
  }

  handleDecreaseValue() {
    this.updateStep(-1);
  }

  hasError() {
    const { meta } = this.props;
    return Boolean(meta && meta.error && meta.touched);
  }

  formatNumber(number) {
    const { precision } = this.props;

    let formattedNumber = this.toNumber(number);

    if (precision !== null) {
      formattedNumber = number.toFixed(precision);
    }

    return String(formattedNumber);
  }

  updateStep(n) {
    const { step } = this.props;

    this.setState(prevState => {
      const number = this.toNumber((prevState.value || 0) + step * n);

      return { value: number };
    });
  }

  toNumber(number) {
    const { max, min, precision } = this.props;

    let float = parseFloat(number);

    if (isNaN(float) || !isFinite(float)) {
      float = 0;
    }

    const baseExponent = Math.pow(10, precision === null ? 10 : precision);

    float = Math.min(Math.max(float, min), max);
    float = Math.round(float * baseExponent) / baseExponent;

    return float;
  }

  renderInput() {
    const { autoFocus, bold, disabled, id, max, min, onBlur, onFocus, placeholder, readOnly, step, type } = this.props;
    const classNames = cx(theme['input'], {
      [theme['is-bold']]: bold,
    });

    const props = {
      autoFocus,
      className: classNames,
      disabled: disabled,
      id,
      max,
      min,
      onBlur: onBlur,
      onChange: this.handleChange,
      onFocus: onFocus,
      placeholder,
      readOnly,
      step,
      type,
      value: type === 'number' && this.state.value ? this.formatNumber(this.state.value) : this.state.value,
    };

    return <input {...props} />;
  }

  renderCounter() {
    if (this.props.counter) {
      return <Counter className={theme['counter']} count={this.props.counter} color="ruby" size="small" />;
    }
  }

  renderHelpText() {
    const { helpText, inverse } = this.props;

    if (helpText) {
      return (
        <TextSmall color={inverse ? 'white' : 'neutral'} marginTop={1} soft>
          {helpText}
        </TextSmall>
      );
    }
  }

  renderSpinnerControls() {
    const { disabled, readOnly, type } = this.props;

    const props = {
      disabled: disabled || readOnly,
    };

    if (type === 'number') {
      return (

          <Button
            className={theme['spinner-up']}
            icon={<IconChevronUpSmallOutline />}
            onClick={this.handleIncreaseValue}
            {...props}
          />
          <Button
            className={theme['spinner-down']}
            icon={<IconChevronDownSmallOutline />}
            onClick={this.handleDecreaseValue}
            {...props}
          />

      );
    }
  }

  renderValidationMessage() {
    return (
      <Box marginTop={2}>
        <IconWarningBadgedSmallFilled className={theme['validation-icon']} />
        <TextSmall className={theme['validation-text']} element="span" marginLeft={1}>
          {this.props.meta.error}
        </TextSmall>
      </Box>
    );
  }

  render() {
    const {
      className,
      connectedLeft,
      connectedRight,
      counter,
      disabled,
      icon,
      iconPlacement,
      inverse,
      size,
      type,
      readOnly,
      ...others
    } = this.props;

    const classNames = cx(
      theme['wrapper'],
      theme[`is-${size}`],
      {
        [theme[`has-icon-${iconPlacement}`]]: icon,
        [theme['has-counter']]: counter,
        [theme['has-error']]: this.hasError(),
        [theme['has-connected-left']]: connectedLeft,
        [theme['has-connected-right']]: connectedRight,
        [theme['is-inverse']]: inverse,
        [theme['is-numeric']]: type === 'number',
        [theme['is-disabled']]: disabled,
        [theme['is-read-only']]: readOnly,
      },
      className,
    );

    const inputWrapperClassnames = cx(theme['input-wrapper'], {
      [theme['has-error']]: this.hasError(),
    });

    const rest = omit(others, [
      'bold',
      'id',
      'helpText',
      'max',
      'meta',
      'min',
      'onBlur',
      'onChange',
      'onFocus',
      'placeholder',
      'precision',
      'updateStep',
      'value',
    ]);

    return (
      <Box className={classNames} {...rest}>

          {connectedLeft}

            {icon &&
              createElement(icon, {
                className: theme['icon'],
              })}
            {this.renderInput()}
            {this.renderCounter()}
            {this.renderSpinnerControls()}

          {connectedRight}

        {this.hasError() ? this.renderValidationMessage() : this.renderHelpText()}
      </Box>
    );
  }
}
<commit_msg>Fix falsy values (e.g. `""`, `0`) not triggering the `onChange` handler<commit_after>import React, { Component, createElement } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import omit from 'lodash.omit';
import {
  IconChevronUpSmallOutline,
  IconChevronDownSmallOutline,
  IconWarningBadgedSmallFilled,
} from '@teamleader/ui-icons';
import InputMetaPropTypes from './InputMetaPropTypes';
import Box from '../box';
import Button from '../button';
import Counter from '../counter';
import { TextSmall } from '../typography';
import theme from './theme.css';

export default class Input extends Component {
  static propTypes = {
    autoFocus: PropTypes.bool,
    /** Boolean indicating whether the input text should render in bold. */
    bold: PropTypes.bool,
    /** Sets a class name to give custom styles. */
    className: PropTypes.string,
    connectedLeft: PropTypes.element,
    connectedRight: PropTypes.element,
    /** The number to render as a counter inside the input. */
    counter: PropTypes.number,
    /** Boolean indicating whether the input should render as disabled. */
    disabled: PropTypes.bool,
    /** The text string to use as help text below the input. */
    helpText: PropTypes.string,
    /** The icon displayed inside the input. */
    icon: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
    /** The position of the icon inside the input. */
    iconPlacement: PropTypes.oneOf(['left', 'right']),
    /** The text string to use as input identifier. */
    id: PropTypes.string,
    /** Boolean indicating whether the input should render as inverse. */
    inverse: PropTypes.bool,
    max: PropTypes.number,
    min: PropTypes.number,
    /** Object to provide meta information for redux forms. */
    meta: InputMetaPropTypes,
    /** Callback function that is fired when component is blurred. */
    onBlur: PropTypes.func,
    /** Callback function that is fired when the component's value changes. */
    onChange: PropTypes.func,
    /** Callback function that is fired when component is focused. */
    onFocus: PropTypes.func,
    /** The text string to use as placeholder. */
    placeholder: PropTypes.string,
    precision: PropTypes.number,
    /** Boolean indicating whether the input should render as read only. */
    readOnly: PropTypes.bool,
    /** Size of the input element. */
    size: PropTypes.oneOf(['small', 'medium', 'large']),
    /** Limit increment value for numeric inputs. */
    step: PropTypes.number,
    /** Type of the input element. It can be a valid HTML5 input type. */
    type: PropTypes.string,
    /** Current value of the input element. */
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  };

  static defaultProps = {
    iconPlacement: 'left',
    inverse: false,
    disabled: false,
    placeholder: '',
    precision: null,
    min: Number.MIN_SAFE_INTEGER,
    max: Number.MAX_SAFE_INTEGER,
    readOnly: false,
    size: 'medium',
    step: 1,
  };

  constructor(props) {
    super(props);

    this.handleChange = ::this.handleChange;
    this.handleIncreaseValue = ::this.handleIncreaseValue;
    this.handleDecreaseValue = ::this.handleDecreaseValue;

    this.state = {
      value: props.type === 'number' ? Number(props.value) || '' : props.value || '',
    };
  }

  componentWillUpdate(props, state) {
    if (props.input && props.input.onChange && state.value !== undefined) {
      props.input.onChange(state.value);
    }
  }

  handleChange(event) {
    const value = event.target.value;

    this.setState(() => ({
      value: this.props.type === 'number' ? Number(value) : value,
    }));
  }

  handleIncreaseValue() {
    this.updateStep(1);
  }

  handleDecreaseValue() {
    this.updateStep(-1);
  }

  hasError() {
    const { meta } = this.props;
    return Boolean(meta && meta.error && meta.touched);
  }

  formatNumber(number) {
    const { precision } = this.props;

    let formattedNumber = this.toNumber(number);

    if (precision !== null) {
      formattedNumber = number.toFixed(precision);
    }

    return String(formattedNumber);
  }

  updateStep(n) {
    const { step } = this.props;

    this.setState(prevState => {
      const number = this.toNumber((prevState.value || 0) + step * n);

      return { value: number };
    });
  }

  toNumber(number) {
    const { max, min, precision } = this.props;

    let float = parseFloat(number);

    if (isNaN(float) || !isFinite(float)) {
      float = 0;
    }

    const baseExponent = Math.pow(10, precision === null ? 10 : precision);

    float = Math.min(Math.max(float, min), max);
    float = Math.round(float * baseExponent) / baseExponent;

    return float;
  }

  renderInput() {
    const { autoFocus, bold, disabled, id, max, min, onBlur, onFocus, placeholder, readOnly, step, type } = this.props;
    const classNames = cx(theme['input'], {
      [theme['is-bold']]: bold,
    });

    const props = {
      autoFocus,
      className: classNames,
      disabled: disabled,
      id,
      max,
      min,
      onBlur: onBlur,
      onChange: this.handleChange,
      onFocus: onFocus,
      placeholder,
      readOnly,
      step,
      type,
      value: type === 'number' && this.state.value ? this.formatNumber(this.state.value) : this.state.value,
    };

    return <input {...props} />;
  }

  renderCounter() {
    if (this.props.counter) {
      return <Counter className={theme['counter']} count={this.props.counter} color="ruby" size="small" />;
    }
  }

  renderHelpText() {
    const { helpText, inverse } = this.props;

    if (helpText) {
      return (
        <TextSmall color={inverse ? 'white' : 'neutral'} marginTop={1} soft>
          {helpText}
        </TextSmall>
      );
    }
  }

  renderSpinnerControls() {
    const { disabled, readOnly, type } = this.props;

    const props = {
      disabled: disabled || readOnly,
    };

    if (type === 'number') {
      return (

          <Button
            className={theme['spinner-up']}
            icon={<IconChevronUpSmallOutline />}
            onClick={this.handleIncreaseValue}
            {...props}
          />
          <Button
            className={theme['spinner-down']}
            icon={<IconChevronDownSmallOutline />}
            onClick={this.handleDecreaseValue}
            {...props}
          />

      );
    }
  }

  renderValidationMessage() {
    return (
      <Box marginTop={2}>
        <IconWarningBadgedSmallFilled className={theme['validation-icon']} />
        <TextSmall className={theme['validation-text']} element="span" marginLeft={1}>
          {this.props.meta.error}
        </TextSmall>
      </Box>
    );
  }

  render() {
    const {
      className,
      connectedLeft,
      connectedRight,
      counter,
      disabled,
      icon,
      iconPlacement,
      inverse,
      size,
      type,
      readOnly,
      ...others
    } = this.props;

    const classNames = cx(
      theme['wrapper'],
      theme[`is-${size}`],
      {
        [theme[`has-icon-${iconPlacement}`]]: icon,
        [theme['has-counter']]: counter,
        [theme['has-error']]: this.hasError(),
        [theme['has-connected-left']]: connectedLeft,
        [theme['has-connected-right']]: connectedRight,
        [theme['is-inverse']]: inverse,
        [theme['is-numeric']]: type === 'number',
        [theme['is-disabled']]: disabled,
        [theme['is-read-only']]: readOnly,
      },
      className,
    );

    const inputWrapperClassnames = cx(theme['input-wrapper'], {
      [theme['has-error']]: this.hasError(),
    });

    const rest = omit(others, [
      'bold',
      'id',
      'helpText',
      'max',
      'meta',
      'min',
      'onBlur',
      'onChange',
      'onFocus',
      'placeholder',
      'precision',
      'updateStep',
      'value',
    ]);

    return (
      <Box className={classNames} {...rest}>

          {connectedLeft}

            {icon &&
              createElement(icon, {
                className: theme['icon'],
              })}
            {this.renderInput()}
            {this.renderCounter()}
            {this.renderSpinnerControls()}

          {connectedRight}

        {this.hasError() ? this.renderValidationMessage() : this.renderHelpText()}
      </Box>
    );
  }
}
<|endoftext|>