<commit_before>/*global YUI: true*/
/*jslint nomen: true*/
YUI.add('node++', function (Y) {
    function showBlock() {
        this.setStyle('display', 'block');
    }

    Y.Node.addMethod('showBlock', showBlock);

    Y.NodeList.importMethod(Y.Node.prototype, 'showBlock');
}, {requires: ['node']});

YUI.add('FinancistoApp', function (Y) {
    // HELPERS
    function convertedDataSetter() {
        var data = this.get('json'),
            output = {};

        [
            'account',
            'transactions',
            'currency',
            'locations',
            'project',
            'transactions',
            'payee'
        ].forEach(function (key) {
            var outputKey = key;
            if (key === 'account') {
                outputKey = 'accounts';
            } else if (key === 'project') {
                outputKey = 'projects';
            } else if (key === 'currency') {
                outputKey = 'currencies';
            }
            output[outputKey] = {};
            if (data[key]) {
                data[key].forEach(function (item) {
                    output[outputKey][item._id] = item;
                });
            }
        });
        return output;
    }
    function createListItem(acc) {
        var listItem = Y.Node.create('
'),
            btn = Y.Node.create('<button>');
        btn.set('type', 'button');
        btn.setData('accountid', acc.title);
        btn.addClass('visible');
        btn.insert(' ');
        btn.insert(acc.title);
        listItem.insert(btn);
        return listItem;
    }
    function load(elem) {
        elem = Y.one(elem);
        var loader = elem.one('.loader');
        if (!loader) {
            loader = Y.Node.create('
');
            loader.addClass('loader');
            loader.hide();
            elem.insert(loader);
        }
        loader.setStyles({
            width: elem.getStyle('width'),
            height: elem.getStyle('height')
        });
        elem.setStyle('position', 'relative');
        loader.showBlock();
    }
    function generateTransactionLabel(categoryItem, valueItem, itemIndex, series, seriesIndex) {
        return '
' +
            '
' +
            '
Payee name: ' + valueItem.displayName + '
' +
            '
Amount: ' + valueItem.value + '
' +
            '
Date: ' + categoryItem.value + '
' +
            '

';
    }
    function processAllTransactions() {
        var date = new Date(),
            json = this.get('json'),
            data = this.get('convertedData'),
            amounts = {},
            dates = {},
            total = 0;

        json.account.forEach(function (account) {
            amounts[data.accounts[account._id].title] = 0;
        });

        json.transactions.forEach(function (transaction) {
            var from_change,
                title = data.accounts[transaction.from_account_id].title,
                to_change,
                dateStr,
                ob;
            date.setTime(transaction.datetime);
            dateStr = [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-');
            from_change = transaction.from_amount / 100;
            amounts[title] += from_change;
            total += from_change;
            if (transaction.to_account_id && data.accounts[transaction.to_account_id]) {
                to_change = transaction.to_amount / 100;
                amounts[title] += to_change;
                total += to_change;
            }

            if (!dates[dateStr]) {
                dates[dateStr] = {
                    data: {}
                };
            }
            Object.keys(data.accounts).forEach(function (account) {
                var title = data.accounts[account].title;
                if (!dates[dateStr].data[title]) {
                    dates[dateStr].data[title] = 0;
                }
                dates[dateStr].data[title] = amounts[title];
            });
            dates[dateStr].total = total;
        });

        return {
            total: total,
            dates: dates
        };
    }

    function processTransactions(jsonStr) {
        var json, fields, dates;

        json = JSON.parse(jsonStr);

        fields = {};
        dates = {};
        json.forEach(function (transaction) {
            var date = new Date(), dateStr;
            date.setTime(transaction.datetime);
            dateStr = [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-');
            if (!dates[dateStr]) {
                dates[dateStr] = {};
            }
            if (!dates[dateStr][transaction.payee_title]) {
                dates[dateStr][transaction.payee_title] = 0;
                fields[transaction.payee_title] = 0;
            }
            dates[dateStr][transaction.payee_title] += +transaction.transaction_amount / 100;
        });

        return {
            dates: dates,
            fields: fields
        };
    }
    function onFailure(id, o, args) {
        var msg = null, response;
        try {
            response = JSON.parse(o.response);
            msg = response.message;
            this.badData(msg);
        } catch (er) {
            this.serverError();
        }
        this.noLoad('#DataPoster');
    }

    function hideError() {
        Y.one('#Error').get('parentNode').hide();
    }

    function showError() {
        Y.one('#Error').get('parentNode').showBlock();
    }

    function FinancistoApp() {
        this.addAttrs({
            json: {
                value: null,
                lazyAdd: true
            },
            convertedData: {
                value: null,
                lazyAdd: true,
                setter: convertedDataSetter
            },
            processedAllTransactions: {
                value: null,
                lazyAdd: true,
                setter: processAllTransactions
            },
            processedTransactions: {
                lazyAdd: true,
                setter: processTransactions
            }
        });

        Y.one('#DataPoster').on('submit', function (e) {
            e.preventDefault();
            load('#DataPoster');
            Y.io('/data', {
                method: 'PUT',
                form: {
                    id: 'DataPoster',
                    useDisabled: false
                },
                on: {
                    success: function () {
                        this.noLoad('#DataPoster');
                        this.goodData();
                        this.getAll();
                    }.bind(this),
                    failure: onFailure.bind(this)
                }
            });
        }.bind(this));
        hideError();
        this.tabView = new Y.TabView({
            srcNode: '#Tabs'
        });
        this.tabView.render();
        this.getAll();
        Y.all('.reset').on('click', function (e) {
            e.preventDefault();
            Y.io('/data', {
                method: 'DELETE',
                on: {
                    success: function () {
                        this.hideCharts();
                    }.bind(this),
                    failure: function (response) {
                        this.badData();
                    }.bind(this)
                }
            });
        }.bind(this));
    }
    FinancistoApp.prototype = {
        colors: [
            '#4572A7',
            '#AA4643',
            '#89A54E',
            '#80699B',
            '#3D96AE',
            '#DB843D',
            '#92A8CD',
            '#A47D7C',
            '#B5CA92'
        ],
        load: function load(elem) {
            elem = Y.one(elem);
            var loader = elem.one('.loader');
            if (!loader) {
                loader = Y.Node.create('
');
                loader.addClass('loader');
                loader.hide();
                elem.insert(loader);
            }
            loader.setStyles({
                width: elem.getStyle('width'),
                height: elem.getStyle('height')
            });
            elem.setStyle('position', 'relative');
            loader.showBlock();
        },
        noLoad: function noLoad(elem) {
            elem = Y.one(elem);
            var loader = elem.one('.loader');
            if (loader) {
                loader.hide();
            }
        },
        getTotalTransactions: function () {
            var processedAllTransactions, dates;

            processedAllTransactions = this.get('processedAllTransactions');
            dates = processedAllTransactions.dates;

            return Object.keys(dates).map(function (item) {
                return {
                    date: item,
                    total: dates[item].total
                };
            });
        },
        generateTransactions: function () {
            var processedAllTransactions, dates;

            processedAllTransactions = this.get('processedAllTransactions');
            dates = processedAllTransactions.dates;

            return Object.keys(dates).map(function (item) {
                var ob = dates[item].data;
                ob.date = item;
                return ob;
            });
        },
        createChart: function createChart(conf, data) {
            var defaultConf, chartConf, chart, seriesStyles, colorIndex;
            defaultConf = {
                dataProvider: data,
                categoryKey: 'date',
                type: 'combo',
                tooltip: {
                    styles: {
                        borderRadius: '5px',
                        backgroundColor: '#333',
                        borderColor: '#000',
                        color: '#fff',
                        fontFamily: 'sans-serif',
                        fontSize: '13px',
                        padding: '2px 5px'
                    }
                },
                verticalGridlines: {
                    styles: {
                        line: {
                            color: '#aaa',
                            width: 1

                        }
                    }
                },
                horizontalGridlines: {
                    styles: {
                        line: {
                            color: '#aaa',
                            width: 1
                        }
                    }
                }
            };
            chartConf = Y.merge(defaultConf, conf);
            seriesStyles = {};
            colorIndex = 0;
            Object.keys(chartConf.dataProvider[0]).forEach(function (key) {
                if (key !== chartConf.categoryKey) {
                    colorIndex += 1;
                    if (this.colors.length - 1 < colorIndex) {
                        colorIndex = 0;
                    }
                    var color = this.colors[colorIndex];
                    seriesStyles[key] = {
                        line: {
                            color: color
                        },
                        marker: {
                            fill: {
                                color: color
                            },
                            border: {
                                color: color
                            }
                        }
                    };
                }
            }.bind(this));

            chartConf = Y.merge(chartConf, {styles: {series: seriesStyles}});
            chart = new Y.Chart(chartConf);
            chart.getAxisByKey('date').set('styles', {
                label: {
                    rotation: -45
                },
                line: {
                    width: 2
                }
            });
            return chart;
        },
        addChartControls: function () {
            var list,
                data = this.get('convertedData');

            list = Y.Node.create('
');

            Object.keys(data.accounts).forEach(function (account) {
                list.insert(createListItem(data.accounts[account]));
            });

            list.delegate('click', function (e) {
                var series = this.allDataChart.getSeries(e.target.getData('accountid'));
                series.set('visible', !series.get('visible'));
                if (series.get('visible')) {
                    e.target.replaceClass('hidden', 'visible');
                } else {
                    e.target.replaceClass('visible', 'hidden');
                }
                this.allDataChart.render();
            }.bind(this), 'button');

            list.all('button').each(function (item) {
                var series = this.allDataChart.getSeries(item.getData('accountid')),
                    styles;
                if (series) {
                    styles = series.get('styles');
                    if (styles) {
                        item.one('span').setStyle('backgroundColor', styles.marker.fill.color);
                    }
                }
            }.bind(this));

            Y.one('#Controls').insert(list);
        },
        onAllResponse: function onAllResponse(json) {
            var trn = this.generateTransactions(),
                totalTransactions,
                min,
                max;

            this.allDataChart = this.createChart({
                render: "#Mychart"
            }, trn);
            totalTransactions = this.getTotalTransactions();
            this.totalChart = this.createChart({
                render: '#Totalchart'
            }, totalTransactions);
            min = totalTransactions[0].total;
            max = totalTransactions[0].total;
            totalTransactions.forEach(function (transaction) {
                min = transaction.total < min ? transaction.total : min;
                max = transaction.total > max ? transaction.total : max;
            });
            this.totalChart.get('axes').values.set('minimum', min - Math.round(min / 10));
            this.totalChart.get('axes').values.set('maximum', max + Math.round(min / 10));
            this.addChartControls();
        },
        onTransactionsResponse: function onTransactionsResponse(id, o, args) {
            this.noLoad('#DataPoster');
            this.set('processedTransactions', o.response);
            var transactions, trn, fields, dates;

            trn = [];
            transactions = this.get('processedTransactions');
            fields = transactions.fields;
            dates = transactions.dates;

            Object.keys(dates).forEach(function (date) {
                var ob = dates[date];
                ob.date = date;
                trn.push(Y.merge(fields, ob));
            });
            this.transactionsChart = this.createChart({
                render: "#Transactions",
                type: 'column',
                stacked: true,
                tooltip: {
                    styles: {
                        borderRadius: '5px',
                        backgroundColor: '#333',
                        borderColor: '#000'
                    },
                    markerLabelFunction: generateTransactionLabel
                }
            }, trn);
            this.showCharts();
        },
        showCharts: function showCharts() {
            Y.one('#Charts').showBlock();
            Y.one('#DataForm').hide();
        },
        hideCharts: function hideCharts() {
            Y.one('#Charts').hide();
            Y.one('#DataForm').showBlock();
            Y.one('#Controls').empty();
            if (this.allDataChart) {
                this.allDataChart.destroy();
            }
            if (this.transactionsChart) {
                this.transactionsChart.destroy();
            }
            if (this.totalChart) {
                this.totalChart.destroy();
            }
        },
        badData: function badData(msg) {
            this.hideCharts();
            msg = msg || 'Bad data';
            Y.one('#Error').setContent(msg);
            showError();
        },
        goodData: function goodData() {
            this.showCharts();
            hideError();
        },
        serverError: function serverError() {
            this.hideCharts();
            Y.one('#Error').setContent('Server error');
            showError();
        },
        getTransactions: function getTransactions() {
            load('#DataPoster');
            Y.io('transactions.json?days=31', {
                'on': {
                    'success': this.onTransactionsResponse.bind(this),
                    'failure': function () {
                        this.noLoad('#DataPoster');
                    }.bind(this)

                }
            });
        },
        getAll: function getAll() {
            load('#DataPoster');
            Y.io('/data', {
                data: {
                    d: Date.now()
                },
                'on': {
                    'success': function (id, o, args) {

                        this.noLoad('#DataPoster');
                        if (o.status === 200) {
                            var json = JSON.parse(o.response);
                            if (json.account) {
                                this.set('json', json);
                                this.set('processedAllTransactions');
                                this.getTransactions();
                                this.goodData();
                                this.onAllResponse(json);
                            } else {
                                this.badData();
                            }
                        } else if (o.status === 204) {
                            this.badData('Please upload a backup file');
                        }
                    }.bind(this),
                    'failure': onFailure.bind(this)
                }
            });
        }
    };

    window.Y = Y;

    Y.augment(FinancistoApp, Y.Attribute);
    Y.FinancistoApp = FinancistoApp;
}, '0.0.1', {
    requires: ['base', 'charts', 'io', 'node++', 'event', 'tabview', 'console']
});

<commit_msg>Sort transactions before parse them<commit_after>/*global YUI: true*/
/*jslint nomen: true*/
YUI.add('node++', function (Y) {
    function showBlock() {
        this.setStyle('display', 'block');
    }

    Y.Node.addMethod('showBlock', showBlock);

    Y.NodeList.importMethod(Y.Node.prototype, 'showBlock');
}, {requires: ['node']});

YUI.add('FinancistoApp', function (Y) {
    // HELPERS
    function convertedDataSetter() {
        var data = this.get('json'),
            output = {};

        [
            'account',
            'transactions',
            'currency',
            'locations',
            'project',
            'transactions',
            'payee'
        ].forEach(function (key) {
            var outputKey = key;
            if (key === 'account') {
                outputKey = 'accounts';
            } else if (key === 'project') {
                outputKey = 'projects';
            } else if (key === 'currency') {
                outputKey = 'currencies';
            }
            output[outputKey] = {};
            if (data[key]) {
                data[key].forEach(function (item) {
                    output[outputKey][item._id] = item;
                });
            }
        });
        return output;
    }
    function createListItem(acc) {
        var listItem = Y.Node.create('
'),
            btn = Y.Node.create('<button>');
        btn.set('type', 'button');
        btn.setData('accountid', acc.title);
        btn.addClass('visible');
        btn.insert(' ');
        btn.insert(acc.title);
        listItem.insert(btn);
        return listItem;
    }
    function load(elem) {
        elem = Y.one(elem);
        var loader = elem.one('.loader');
        if (!loader) {
            loader = Y.Node.create('
');
            loader.addClass('loader');
            loader.hide();
            elem.insert(loader);
        }
        loader.setStyles({
            width: elem.getStyle('width'),
            height: elem.getStyle('height')
        });
        elem.setStyle('position', 'relative');
        loader.showBlock();
    }
    function generateTransactionLabel(categoryItem, valueItem, itemIndex, series, seriesIndex) {
        return '
' +
            '
' +
            '
Payee name: ' + valueItem.displayName + '
' +
            '
Amount: ' + valueItem.value + '
' +
            '
Date: ' + categoryItem.value + '
' +
            '

';
    }
    function processAllTransactions() {
        var date = new Date(),
            json = this.get('json'),
            data = this.get('convertedData'),
            amounts = {},
            dates = {},
            total = 0;

        json.account.forEach(function (account) {
            amounts[data.accounts[account._id].title] = 0;
        });

        json.transactions.sort(function (a, b) {
            return a.datetime - b.datetime;
        });

        json.transactions.forEach(function (transaction) {
            var from_change,
                title = data.accounts[transaction.from_account_id].title,
                to_change = 0,
                change = 0,
                dateStr,
                ob;
            date.setTime(transaction.datetime);
            dateStr = [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-');
            from_change = transaction.from_amount / 100;
            if (transaction.to_account_id && data.accounts[transaction.to_account_id]) {
                to_change = transaction.to_amount / 100;
            }
            change = (from_change + to_change);
            amounts[title] += change;
            total += change;
            if (!dates[dateStr]) {
                dates[dateStr] = {
                    change: 0,
                    data: {}
                };
            }
            dates[dateStr].change = (from_change + to_change);
            Object.keys(data.accounts).forEach(function (account) {
                var title = data.accounts[account].title;
                if (!dates[dateStr].data[title]) {
                    dates[dateStr].data[title] = 0;
                }
                dates[dateStr].data[title] = amounts[title];
            });
            dates[dateStr].total = total;
        });

        return {
            total: total,
            dates: dates
        };
    }

    function processTransactions(jsonStr) {
        var json, fields, dates;

        json = JSON.parse(jsonStr);

        fields = {};
        dates = {};
        json.forEach(function (transaction) {
            var date = new Date(), dateStr;
            date.setTime(transaction.datetime);
            dateStr = [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-');
            if (!dates[dateStr]) {
                dates[dateStr] = {};
            }
            if (!dates[dateStr][transaction.payee_title]) {
                dates[dateStr][transaction.payee_title] = 0;
                fields[transaction.payee_title] = 0;
            }
            dates[dateStr][transaction.payee_title] += +transaction.transaction_amount / 100;
        });

        return {
            dates: dates,
            fields: fields
        };
    }
    function onFailure(id, o, args) {
        var msg = null, response;
        try {
            response = JSON.parse(o.response);
            msg = response.message;
            this.badData(msg);
        } catch (er) {
            this.serverError();
        }
        this.noLoad('#DataPoster');
    }

    function hideError() {
        Y.one('#Error').get('parentNode').hide();
    }

    function showError() {
        Y.one('#Error').get('parentNode').showBlock();
    }

    function FinancistoApp() {
        this.addAttrs({
            json: {
                value: null,
                lazyAdd: true
            },
            convertedData: {
                value: null,
                lazyAdd: true,
                setter: convertedDataSetter
            },
            processedAllTransactions: {
                value: null,
                lazyAdd: true,
                setter: processAllTransactions
            },
            processedTransactions: {
                lazyAdd: true,
                setter: processTransactions
            }
        });

        Y.one('#DataPoster').on('submit', function (e) {
            e.preventDefault();
            load('#DataPoster');
            Y.io('/data', {
                method: 'PUT',
                form: {
                    id: 'DataPoster',
                    useDisabled: false
                },
                on: {
                    success: function () {
                        this.noLoad('#DataPoster');
                        this.goodData();
                        this.getAll();
                    }.bind(this),
                    failure: onFailure.bind(this)
                }
            });
        }.bind(this));
        hideError();
        this.tabView = new Y.TabView({
            srcNode: '#Tabs'
        });
        this.tabView.render();
        this.getAll();
        Y.all('.reset').on('click', function (e) {
            e.preventDefault();
            Y.io('/data', {
                method: 'DELETE',
                on: {
                    success: function () {
                        this.hideCharts();
                    }.bind(this),
                    failure: function (response) {
                        this.badData();
                    }.bind(this)
                }
            });
        }.bind(this));
    }
    FinancistoApp.prototype = {
        colors: [
            '#4572A7',
            '#AA4643',
            '#89A54E',
            '#80699B',
            '#3D96AE',
            '#DB843D',
            '#92A8CD',
            '#A47D7C',
            '#B5CA92'
        ],
        load: function load(elem) {
            elem = Y.one(elem);
            var loader = elem.one('.loader');
            if (!loader) {
                loader = Y.Node.create('
');
                loader.addClass('loader');
                loader.hide();
                elem.insert(loader);
            }
            loader.setStyles({
                width: elem.getStyle('width'),
                height: elem.getStyle('height')
            });
            elem.setStyle('position', 'relative');
            loader.showBlock();
        },
        noLoad: function noLoad(elem) {
            elem = Y.one(elem);
            var loader = elem.one('.loader');
            if (loader) {
                loader.hide();
            }
        },
        getTotalTransactions: function () {
            var processedAllTransactions, dates;

            processedAllTransactions = this.get('processedAllTransactions');
            dates = processedAllTransactions.dates;

            return Object.keys(dates).map(function (item) {
                return {
                    date: item,
                    total: dates[item].total
                };
            });
        },
        generateTransactions: function () {
            var processedAllTransactions, dates;

            processedAllTransactions = this.get('processedAllTransactions');
            dates = processedAllTransactions.dates;

            return Object.keys(dates).map(function (item) {
                var ob = dates[item].data;
                ob.date = item;
                return ob;
            });
        },
        createChart: function createChart(conf, data) {
            var defaultConf, chartConf, chart, seriesStyles, colorIndex;
            defaultConf = {
                dataProvider: data,
                categoryKey: 'date',
                type: 'combo',
                tooltip: {
                    styles: {
                        borderRadius: '5px',
                        backgroundColor: '#333',
                        borderColor: '#000',
                        color: '#fff',
                        fontFamily: 'sans-serif',
                        fontSize: '13px',
                        padding: '2px 5px'
                    }
                },
                verticalGridlines: {
                    styles: {
                        line: {
                            color: '#aaa',
                            width: 1

                        }
                    }
                },
                horizontalGridlines: {
                    styles: {
                        line: {
                            color: '#aaa',
                            width: 1
                        }
                    }
                }
            };
            chartConf = Y.merge(defaultConf, conf);
            seriesStyles = {};
            colorIndex = 0;
            Object.keys(chartConf.dataProvider[0]).forEach(function (key) {
                if (key !== chartConf.categoryKey) {
                    colorIndex += 1;
                    if (this.colors.length - 1 < colorIndex) {
                        colorIndex = 0;
                    }
                    var color = this.colors[colorIndex];
                    seriesStyles[key] = {
                        line: {
                            color: color
                        },
                        marker: {
                            fill: {
                                color: color
                            },
                            border: {
                                color: color
                            }
                        }
                    };
                }
            }.bind(this));

            chartConf = Y.merge(chartConf, {styles: {series: seriesStyles}});
            chart = new Y.Chart(chartConf);
            chart.getAxisByKey('date').set('styles', {
                label: {
                    rotation: -45
                },
                line: {
                    width: 2
                }
            });
            return chart;
        },
        addChartControls: function () {
            var list,
                data = this.get('convertedData');

            list = Y.Node.create('
');

            Object.keys(data.accounts).forEach(function (account) {
                list.insert(createListItem(data.accounts[account]));
            });

            list.delegate('click', function (e) {
                var series = this.allDataChart.getSeries(e.target.getData('accountid'));
                series.set('visible', !series.get('visible'));
                if (series.get('visible')) {
                    e.target.replaceClass('hidden', 'visible');
                } else {
                    e.target.replaceClass('visible', 'hidden');
                }
                this.allDataChart.render();
            }.bind(this), 'button');

            list.all('button').each(function (item) {
                var series = this.allDataChart.getSeries(item.getData('accountid')),
                    styles;
                if (series) {
                    styles = series.get('styles');
                    if (styles) {
                        item.one('span').setStyle('backgroundColor', styles.marker.fill.color);
                    }
                }
            }.bind(this));

            Y.one('#Controls').insert(list);
        },
        onAllResponse: function onAllResponse(json) {
            var trn = this.generateTransactions(),
                totalTransactions,
                min,
                max;

            this.allDataChart = this.createChart({
                render: "#Mychart"
            }, trn);
            totalTransactions = this.getTotalTransactions();
            this.totalChart = this.createChart({
                render: '#Totalchart'
            }, totalTransactions);
            min = totalTransactions[0].total;
            max = totalTransactions[0].total;
            totalTransactions.forEach(function (transaction) {
                min = transaction.total < min ? transaction.total : min;
                max = transaction.total > max ? transaction.total : max;
            });
            this.totalChart.get('axes').values.set('minimum', min - Math.round(min / 10));
            this.totalChart.get('axes').values.set('maximum', max + Math.round(min / 10));
            this.addChartControls();
        },
        onTransactionsResponse: function onTransactionsResponse(id, o, args) {
            this.noLoad('#DataPoster');
            this.set('processedTransactions', o.response);
            var transactions, trn, fields, dates;

            trn = [];
            transactions = this.get('processedTransactions');
            fields = transactions.fields;
            dates = transactions.dates;

            Object.keys(dates).forEach(function (date) {
                var ob = dates[date];
                ob.date = date;
                trn.push(Y.merge(fields, ob));
            });
            this.transactionsChart = this.createChart({
                render: "#Transactions",
                type: 'column',
                stacked: true,
                tooltip: {
                    styles: {
                        borderRadius: '5px',
                        backgroundColor: '#333',
                        borderColor: '#000'
                    },
                    markerLabelFunction: generateTransactionLabel
                }
            }, trn);
            this.showCharts();
        },
        showCharts: function showCharts() {
            Y.one('#Charts').showBlock();
            Y.one('#DataForm').hide();
        },
        hideCharts: function hideCharts() {
            Y.one('#Charts').hide();
            Y.one('#DataForm').showBlock();
            Y.one('#Controls').empty();
            if (this.allDataChart) {
                this.allDataChart.destroy();
            }
            if (this.transactionsChart) {
                this.transactionsChart.destroy();
            }
            if (this.totalChart) {
                this.totalChart.destroy();
            }
        },
        badData: function badData(msg) {
            this.hideCharts();
            msg = msg || 'Bad data';
            Y.one('#Error').setContent(msg);
            showError();
        },
        goodData: function goodData() {
            this.showCharts();
            hideError();
        },
        serverError: function serverError() {
            this.hideCharts();
            Y.one('#Error').setContent('Server error');
            showError();
        },
        getTransactions: function getTransactions() {
            load('#DataPoster');
            Y.io('transactions.json?days=31', {
                'on': {
                    'success': this.onTransactionsResponse.bind(this),
                    'failure': function () {
                        this.noLoad('#DataPoster');
                    }.bind(this)

                }
            });
        },
        getAll: function getAll() {
            load('#DataPoster');
            Y.io('/data', {
                data: {
                    d: Date.now()
                },
                'on': {
                    'success': function (id, o, args) {

                        this.noLoad('#DataPoster');
                        if (o.status === 200) {
                            var json = JSON.parse(o.response);
                            if (json.account) {
                                this.set('json', json);
                                this.set('processedAllTransactions');
                                this.getTransactions();
                                this.goodData();
                                this.onAllResponse(json);
                            } else {
                                this.badData();
                            }
                        } else if (o.status === 204) {
                            this.badData('Please upload a backup file');
                        }
                    }.bind(this),
                    'failure': onFailure.bind(this)
                }
            });
        }
    };

    window.Y = Y;

    Y.augment(FinancistoApp, Y.Attribute);
    Y.FinancistoApp = FinancistoApp;
}, '0.0.1', {
    requires: ['base', 'charts', 'io', 'node++', 'event', 'tabview', 'console']
});

<|endoftext|>


<commit_before>/**!
 * cnpmjs.org - controllers/download.js
 *
 * Copyright(c) cnpmjs.org and other contributors.
 * MIT Licensed
 *
 * Authors:
 *  dead_horse <dead_horse@qq.com> (http://deadhorse.me)
 */

'use strict';

/**
 * Module dependencies.
 */

var Log = require('../proxy/module_log');
var SyncModuleWorker = require('../proxy/sync_module_worker');

exports.sync = function *() {
  var username = this.session.name || 'anonymous';
  var name = this.params.name;
  var publish = this.query.publish === 'true';
  var noDep = this.query.nodeps === 'true';
  if (publish && !this.session.isAdmin) {
    this.status = 403;
    this.body = {
      error: 'no_perms',
      reason: 'Only admin can publish'
    };
    return;
  }

  var options = {
    publish: publish,
    noDep: noDep,
  };

  var result = yield SyncModuleWorker.sync(name, username, options);

  if (!result.ok) {
    this.status = result.statusCode;
    this.body = result.pkg;
    return;
  }
  this.status = 201;
  this.body = {
    ok: true,
    logId: result.logId
  };
};

exports.getSyncLog = function *(next) {
  var logId = this.params.id;
  var name = this.params.name;
  var offset = Number(this.query.offset) || 0;
  var row = yield Log.get(logId);
  if (!row) {
    return yield next;
  }

  var log = row.log.trim();
  if (offset > 0) {
    log = log.split('\n').slice(offset).join('\n');
  }
  this.body = {ok: true, log: log};
};
<commit_msg>fix sync 404 reason not clear<commit_after>/**!
 * cnpmjs.org - controllers/download.js
 *
 * Copyright(c) cnpmjs.org and other contributors.
 * MIT Licensed
 *
 * Authors:
 *  dead_horse <dead_horse@qq.com> (http://deadhorse.me)
 */

'use strict';

/**
 * Module dependencies.
 */

var Log = require('../proxy/module_log');
var SyncModuleWorker = require('../proxy/sync_module_worker');

exports.sync = function *() {
  var username = this.session.name || 'anonymous';
  var name = this.params.name;
  var publish = this.query.publish === 'true';
  var noDep = this.query.nodeps === 'true';
  if (publish && !this.session.isAdmin) {
    this.status = 403;
    this.body = {
      error: 'no_perms',
      reason: 'Only admin can publish'
    };
    return;
  }

  var options = {
    publish: publish,
    noDep: noDep,
  };

  var result = yield SyncModuleWorker.sync(name, username, options);

  // friendly 404 reason info
  if (result.staticCache === 404) {
    this.status = 404;
    this.body = {
      ok: false,
      reason: 'can not found ' + name + ' in the source registry'
    };
    return;
  }
  if (!result.ok) {
    this.status = result.statusCode;
    this.body = result.pkg;
    return;
  }
  this.status = 201;
  this.body = {
    ok: true,
    logId: result.logId
  };
};

exports.getSyncLog = function *(next) {
  var logId = this.params.id;
  var name = this.params.name;
  var offset = Number(this.query.offset) || 0;
  var row = yield Log.get(logId);
  if (!row) {
    return yield next;
  }

  var log = row.log.trim();
  if (offset > 0) {
    log = log.split('\n').slice(offset).join('\n');
  }
  this.body = {ok: true, log: log};
};
<|endoftext|>


<commit_before>// Copyright 2013-2017, University of Colorado Boulder

/**
 * MultiLine plain text, with alignment.
 * The line break character is '\n'.
 * Specify alignment via the 'align' option.
 * Text node options can be specified to style the text.
 *
 * Example: new MultiLineText( 'Hello\nWorld', { align: 'left', font: new PhetFont(20), fill: 'red' } );
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Text = require( 'SCENERY/nodes/Text' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TMultiLineText = require( 'SCENERY_PHET/TMultiLineText' );

  /**
   *
   * @param {string} text
   * @param {Object} [options]
   * @constructor
   */
  function MultiLineText( text, options ) {

    options = _.extend( {
      font: new PhetFont(),
      align: 'center', // 'center', 'left' or 'right' (as supported by VBox)
      tandem: Tandem.tandemRequired(),
      phetioType: TMultiLineText
    }, options );

    // Normally individual properties from options should be stored rather than the entire options instance,
    // but in this case the options is stored because it must be propagated to child text instances
    this.options = options; // @private

    Node.call( this );

    this._text = null; // @private underscore prefix because it has ES5 set/get
    this.textParent = null; // @private
    this.text = text; // call ES5 setter

    // a11y - set the accessible content with setters since options will be propagated to child text instances
    this.tagName = 'p';
    this.accessibleLabel = text;

    this.mutate( _.omit( options, 'align' ) ); // mutate after removing options that are specific to this subtype
  }

  sceneryPhet.register( 'MultiLineText', MultiLineText );

  return inherit( Node, MultiLineText, {

    /**
     * Sets the text.
     * @param {string} text
     * @public
     */
    setText: function( text ) {

      // save the new text
      this._text = text;

      // parse the text and create {Text[]}
      var self = this;
      var textNodes = StringUtils.embeddedSplit( text, '\n' ).map( function( line ) {

        // create a blank line between consecutive line breaks
        if ( line.length === 0 ) { line = ' '; }

        return new Text( line, _.omit( self.options, 'align', 'maxWidth', 'tandem' ) );
      } );

      // determine where the textParent was, so we can maintain rendering order
      var index = this.textParent ? this.indexOfChild( this.textParent ) : 0;

      // remove the old textParent
      if ( this.textParent ) {
        this.removeChild( this.textParent );
      }

      // add the new textParent
      this.textParent = new VBox( {
        children: textNodes,
        align: this.options.align
      } );
      this.insertChild( index, this.textParent );
    },
    set text( value ) { this.setText( value ); }, // ES5 setter

    /**
     * Gets the text.
     * @returns {string}
     * @public
     */
    getText: function() { return this._text; },
    get text() { return this.getText(); }, // ES5 getter

    /**
     * Sets the fill for all Text nodes.
     * @param {Color|string} fill
     * @public
     */
    setFill: function( fill ) {
      this.options.fill = fill;
      var children = this.textParent.getChildren();
      for ( var i = 0; i < children.length; i++ ) {
        children[ i ].setFill( fill );
      }
    },
    set fill( value ) { this.setFill( value ); }, // ES5 setter

    /**
     * Gets the fill used for the text.
     * @returns {Color|string}
     * @public
     */
    getFill: function() { return this.options.fill; },
    get fill() { return this.getFill(); } // ES5 getter
  } );
} );
<commit_msg>remove instrumentation from AboutDialog, https://github.com/phetsims/phet-io/issues/1166<commit_after>// Copyright 2013-2017, University of Colorado Boulder

/**
 * MultiLine plain text, with alignment.
 * The line break character is '\n'.
 * Specify alignment via the 'align' option.
 * Text node options can be specified to style the text.
 *
 * Example: new MultiLineText( 'Hello\nWorld', { align: 'left', font: new PhetFont(20), fill: 'red' } );
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Text = require( 'SCENERY/nodes/Text' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TMultiLineText = require( 'SCENERY_PHET/TMultiLineText' );

  /**
   *
   * @param {string} text
   * @param {Object} [options]
   * @constructor
   */
  function MultiLineText( text, options ) {

    options = _.extend( {
      font: new PhetFont(),
      align: 'center', // 'center', 'left' or 'right' (as supported by VBox)
      tandem: Tandem.tandemOptional(),
      phetioType: TMultiLineText
    }, options );

    // Normally individual properties from options should be stored rather than the entire options instance,
    // but in this case the options is stored because it must be propagated to child text instances
    this.options = options; // @private

    Node.call( this );

    this._text = null; // @private underscore prefix because it has ES5 set/get
    this.textParent = null; // @private
    this.text = text; // call ES5 setter

    // a11y - set the accessible content with setters since options will be propagated to child text instances
    this.tagName = 'p';
    this.accessibleLabel = text;

    this.mutate( _.omit( options, 'align' ) ); // mutate after removing options that are specific to this subtype
  }

  sceneryPhet.register( 'MultiLineText', MultiLineText );

  return inherit( Node, MultiLineText, {

    /**
     * Sets the text.
     * @param {string} text
     * @public
     */
    setText: function( text ) {

      // save the new text
      this._text = text;

      // parse the text and create {Text[]}
      var self = this;
      var textNodes = StringUtils.embeddedSplit( text, '\n' ).map( function( line ) {

        // create a blank line between consecutive line breaks
        if ( line.length === 0 ) { line = ' '; }

        return new Text( line, _.omit( self.options, 'align', 'maxWidth', 'tandem' ) );
      } );

      // determine where the textParent was, so we can maintain rendering order
      var index = this.textParent ? this.indexOfChild( this.textParent ) : 0;

      // remove the old textParent
      if ( this.textParent ) {
        this.removeChild( this.textParent );
      }

      // add the new textParent
      this.textParent = new VBox( {
        children: textNodes,
        align: this.options.align
      } );
      this.insertChild( index, this.textParent );
    },
    set text( value ) { this.setText( value ); }, // ES5 setter

    /**
     * Gets the text.
     * @returns {string}
     * @public
     */
    getText: function() { return this._text; },
    get text() { return this.getText(); }, // ES5 getter

    /**
     * Sets the fill for all Text nodes.
     * @param {Color|string} fill
     * @public
     */
    setFill: function( fill ) {
      this.options.fill = fill;
      var children = this.textParent.getChildren();
      for ( var i = 0; i < children.length; i++ ) {
        children[ i ].setFill( fill );
      }
    },
    set fill( value ) { this.setFill( value ); }, // ES5 setter

    /**
     * Gets the fill used for the text.
     * @returns {Color|string}
     * @public
     */
    getFill: function() { return this.options.fill; },
    get fill() { return this.getFill(); } // ES5 getter
  } );
} );
<|endoftext|>


<commit_before>define( function( require ) {
  "use strict";

  var Node = require( 'SCENERY/nodes/Node' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var inherit = require( 'PHET_CORE/inherit' );

  var HEIGHT = 40;
  var WIDTH = 981;

  function NavigationBar( tabs, model ) {
    var navigationBar = this;
    var fontSize = 36;
    Node.call( this );

    var textLabels = [];
    _.each( tabs, function( tab ) {
      textLabels.push( new Text( tab.name, {fontSize: fontSize, fill: 'white', centerY: HEIGHT / 2} ) );
    } );
    this.textLabel = new Node();
    this.phetLabel = new Text( "PhET", {fontSize: fontSize, fill: 'yellow', centerY: HEIGHT / 2} );
    this.addChild( this.textLabel );
    this.addChild( this.phetLabel );

    var index = 0;
    var tabChildren = [];
    _.each( tabs, function( tab ) {
      tab.index = index++;
      var child = new Node( {children: [tab.icon]} );
      child.scale( HEIGHT / tab.icon.height );
      child.cursor = 'pointer';
      model.link( 'tab', function( t ) {
        child.invalidateBounds();
        child.opacity = t === tab.index ? 1 : 0.5;
      } );
      child.addInputListener( { down: function() {
        model.tab = tab.index;
        model.home = false;
      }} );
      tabChildren.push( child );
    } );

    this.tabsNode = new HBox( {children: tabChildren, spacing: 7} );
    this.addChild( this.tabsNode );

    this.homeIcon = new FontAwesomeNode( 'home', {cursor: 'pointer', fill: '#fff'} );
    this.homeIcon.centerY = HEIGHT / 2;
    this.homeIcon.addInputListener( {down: function() { model.home = true; }} );
    this.addChild( this.homeIcon );

    _.each( tabs, function( tab ) {
      model.link( 'tab', function( value ) {
        navigationBar.textLabel.children = [ textLabels[value] ];
        navigationBar.tabsNode.centerX = WIDTH / 2;
        navigationBar.phetLabel.right = WIDTH - 5;
        navigationBar.homeIcon.left = navigationBar.tabsNode.right + 15;
        navigationBar.textLabel.right = navigationBar.tabsNode.left - 15;
        navigationBar.textLabel.centerY = HEIGHT / 2;
      } );
    } );
  }

  inherit( NavigationBar, Node );

  return NavigationBar;
} );<commit_msg>cleanup for NavigationBar<commit_after>define( function( require ) {
  "use strict";

  var Node = require( 'SCENERY/nodes/Node' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var inherit = require( 'PHET_CORE/inherit' );

  var HEIGHT = 40;
  var WIDTH = 981;
  var PADDING = 2;

  function NavigationBar( tabs, model ) {
    Node.call( this );
    var navigationBar = this;

    var fontSize = 36;
    var textLabels = [];

    _.each( tabs, function( tab ) {
      textLabels.push( new Text( tab.name, {fontSize: fontSize, fill: 'white', centerY: HEIGHT / 2} ) );
    } );
    this.textLabel = new Node();
    this.phetLabel = new Text( "PhET", {fontSize: fontSize, fill: 'yellow', centerY: HEIGHT / 2, right: WIDTH - 5} );
    this.addChild( this.textLabel );
    this.addChild( this.phetLabel );

    var index = 0;
    var tabChildren = [];
    _.each( tabs, function( tab ) {
      tab.index = index++;
      var child = new Node( {children: [tab.icon], cursor: 'pointer'} );
      child.scale( (HEIGHT - PADDING * 2) / tab.icon.height );
      model.link( 'tab', function( t ) {
        child.invalidateBounds();
        child.opacity = t === tab.index ? 1 : 0.5;
      } );
      child.addInputListener( { down: function() {
        model.tab = tab.index;
        model.home = false;
      }} );
      tabChildren.push( child );
    } );

    //Add the tabs node.  I'm not sure why it must be mutated afterwards, but putting centerX in the constructor options doesn't cause it to end up in the right spot
    this.tabsNode = new HBox( {children: tabChildren, spacing: 7, top: PADDING} ).mutate( {centerX: WIDTH / 2} );
    this.addChild( this.tabsNode );

    this.homeIcon = new FontAwesomeNode( 'home', {cursor: 'pointer', fill: '#fff', centerY: HEIGHT / 2} );
    this.homeIcon.addInputListener( {down: function() { model.home = true; }} );
    this.addChild( this.homeIcon );

    model.link( 'tab', function( value ) {
      navigationBar.textLabel.children = [ textLabels[value] ];
      navigationBar.homeIcon.left = navigationBar.tabsNode.right + 15;
      navigationBar.textLabel.right = navigationBar.tabsNode.left - 15;
    } );
  }

  inherit( NavigationBar, Node );

  return NavigationBar;
} );<|endoftext|>


<commit_before>
var User = require('../proxy').User;
var Topic = require('../proxy').Topic;
var Reply = require('../proxy').Reply;
var TopicCollect = require('../proxy').TopicCollect;
var utility = require('utility');
var util = require('util');

var message = require('../common/message');
var tools = require('../common/tools');
var config = require('../config');
var EventProxy = require('eventproxy');
var validator = require('validator');
var utility = require('utility');
var _ = require('lodash');

exports.index = function (req, res, next) {
  var user_name = req.params.name;
  User.getUserByLoginName(user_name, function (err, user) {
    if (err) {
      return next(err);
    }
    if (!user) {
      res.render('notify/notify', {error: '这个用户不存在。'});
      return;
    }

    var render = function (recent_topics, recent_replies) {
      user.friendly_create_at = tools.formatDate(user.create_at, true);
      // 如果用户没有激活，那么管理员可以帮忙激活
      var token = '';
      if (!user.active && req.session.user && req.session.user.is_admin) {
        token = utility.md5(user.email + user.pass + config.session_secret);
      }
      res.render('user/index', {
        user: user,
        recent_topics: recent_topics,
        recent_replies: recent_replies,
        token: token,
        pageTitle: util.format('@%s 的个人主页', user.loginname),
      });
    };

    var proxy = new EventProxy();
    proxy.assign('recent_topics', 'recent_replies', render);
    proxy.fail(next);

    var query = {author_id: user._id};
    var opt = {limit: 5, sort: '-create_at'};
    Topic.getTopicsByQuery(query, opt, proxy.done('recent_topics'));

    Reply.getRepliesByAuthorId(user._id, {limit: 20, sort: '-create_at'},
      proxy.done(function (replies) {
        var topic_ids = [];
        for (var i = 0; i < replies.length; i++) {
          if (topic_ids.indexOf(replies[i].topic_id.toString()) < 0) {
            topic_ids.push(replies[i].topic_id.toString());
          }
        }
        var query = {_id: {'$in': topic_ids}};
        var opt = {limit: 5, sort: '-create_at'};
        Topic.getTopicsByQuery(query, opt, proxy.done('recent_replies'));
      }));
  });
};

exports.show_stars = function (req, res, next) {
  User.getUsersByQuery({is_star: true}, {}, function (err, stars) {
    if (err) {
      return next(err);
    }
    res.render('user/stars', {stars: stars});
  });
};

exports.showSetting = function (req, res, next) {
  User.getUserById(req.session.user._id, function (err, user) {
    if (err) {
      return next(err);
    }
    if (req.query.save === 'success') {
      user.success = '保存成功。';
    }
    user.error = null;
    return res.render('user/setting', user);
  });
};

exports.setting = function (req, res, next) {
  var ep = new EventProxy();
  ep.fail(next);

  // 显示出错或成功信息
  function showMessage(msg, data, isSuccess) {
    data = data || req.body;
    var data2 = {
      loginname: data.loginname,
      email: data.email,
      url: data.url,
      location: data.location,
      signature: data.signature,
      weibo: data.weibo,
    };
    if (isSuccess) {
      data2.success = msg;
    } else {
      data2.error = msg;
    }
    res.render('user/setting', data2);
  }

  // post
  var action = req.body.action;
  if (action === 'change_setting') {
    var url = validator.trim(req.body.url);
    url = validator.escape(url);
    var location = validator.trim(req.body.location);
    location = validator.escape(location);
    var weibo = validator.trim(req.body.weibo);
    weibo = validator.escape(weibo);
    var signature = validator.trim(req.body.signature);
    signature = validator.escape(signature);

    User.getUserById(req.session.user._id, ep.done(function (user) {
      user.url = url;
      user.location = location;
      user.signature = signature;
      user.weibo = weibo;
      // create gravatar
      user.avatar = User.makeGravatar(user.email);
      user.save(function (err) {
        if (err) {
          return next(err);
        }
        req.session.user = user.toObject({virtual: true});
        return res.redirect('/setting?save=success');
      });
    }));
  }
  if (action === 'change_password') {
    var old_pass = validator.trim(req.body.old_pass);
    var new_pass = validator.trim(req.body.new_pass);
    if (!old_pass || !new_pass) {
      return res.send('旧密码或新密码不得为空');
    }

    User.getUserById(req.session.user._id, ep.done(function (user) {
      tools.bcompare(old_pass, user.pass, ep.done(function (bool) {
        if (!bool) {
          return showMessage('当前密码不正确。', user);
        }

        tools.bhash(new_pass, ep.done(function (passhash) {
          user.pass = passhash;
          user.save(function (err) {
            if (err) {
              return next(err);
            }
            return showMessage('密码已被修改。', user, true);

          });
        }));
      }));
    }));
  }
};

exports.toggle_star = function (req, res, next) {
  var user_id = req.body.user_id;
  User.getUserById(user_id, function (err, user) {
    if (err) {
      return next(err);
    }
    if (!user) {
      return next(new Error('user is not exists'));
    }
    user.is_star = !user.is_star;
    user.save(function (err) {
      if (err) {
        return next(err);
      }
      res.json({ status: 'success' });
    });
  });
};

exports.get_collect_topics = function (req, res, next) {
  var name = req.params.name;
  User.getUserByLoginName(name, function (err, user) {
    if (err || !user) {
      return next(err);
    }

    var page = Number(req.query.page) || 1;
    var limit = config.list_topic_count;

    var render = function (topics, pages) {
      res.render('user/collect_topics', {
        topics: topics,
        current_page: page,
        pages: pages,
        user: user
      });
    };

    var proxy = EventProxy.create('topics', 'pages', render);
    proxy.fail(next);

    TopicCollect.getTopicCollectsByUserId(user._id, proxy.done(function (docs) {
      var ids = [];
      for (var i = 0; i < docs.length; i++) {
        ids.push(docs[i].topic_id);
      }
      var query = { _id: { '$in': ids } };
      var opt = {
        skip: (page - 1) * limit,
        limit: limit,
        sort: '-create_at'
      };
      Topic.getTopicsByQuery(query, opt, proxy.done('topics'));
      Topic.getCountByQuery(query, proxy.done(function (all_topics_count) {
        var pages = Math.ceil(all_topics_count / limit);
        proxy.emit('pages', pages);
      }));
    }));
  });
};

exports.top100 = function (req, res, next) {
  var opt = {limit: 100, sort: '-score'};
  User.getUsersByQuery({'$or': [
    {is_block: {'$exists': false}},
    {is_block: false},
  ]}, opt, function (err, tops) {
    if (err) {
      return next(err);
    }
    res.render('user/top100', {
      users: tops,
      pageTitle: 'top100',
    });
  });
};

exports.list_topics = function (req, res, next) {
  var user_name = req.params.name;
  var page = Number(req.query.page) || 1;
  var limit = config.list_topic_count;

  User.getUserByLoginName(user_name, function (err, user) {
    if (!user) {
      res.render('notify/notify', {error: '这个用户不存在。'});
      return;
    }

    var render = function (topics, pages) {
      user.friendly_create_at = tools.formatDate(user.create_at, true);
      res.render('user/topics', {
        user: user,
        topics: topics,
        current_page: page,
        pages: pages
      });
    };

    var proxy = new EventProxy();
    proxy.assign('topics', 'pages', render);
    proxy.fail(next);

    var query = {'author_id': user._id};
    var opt = {skip: (page - 1) * limit, limit: limit, sort: '-create_at'};
    Topic.getTopicsByQuery(query, opt, proxy.done('topics'));

    Topic.getCountByQuery(query, proxy.done(function (all_topics_count) {
      var pages = Math.ceil(all_topics_count / limit);
      proxy.emit('pages', pages);
    }));
  });
};

exports.list_replies = function (req, res, next) {
  var user_name = req.params.name;
  var page = Number(req.query.page) || 1;
  var limit = 50;

  User.getUserByLoginName(user_name, function (err, user) {
    if (!user) {
      res.render('notify/notify', {error: '这个用户不存在。'});
      return;
    }

    var render = function (topics, pages) {
      user.friendly_create_at = tools.formatDate(user.create_at, true);
      res.render('user/replies', {
        user: user,
        topics: topics,
        current_page: page,
        pages: pages
      });
    };

    var proxy = new EventProxy();
    proxy.assign('topics', 'pages', render);
    proxy.fail(next);

    var opt = {skip: (page - 1) * limit, limit: limit, sort: '-create_at'};
    Reply.getRepliesByAuthorId(user._id, opt, proxy.done(function (replies) {
      // 获取所有有评论的主题
      var topic_ids = replies.map(function (reply) {
        return reply.topic_id;
      });
      topic_ids = _.uniq(topic_ids);
      var query = {'_id': {'$in': topic_ids}};
      Topic.getTopicsByQuery(query, {}, proxy.done('topics'));
    }));

    Reply.getCountByAuthorId(user._id, proxy.done('pages', function (count) {
      var pages = Math.ceil(count / limit);
      return pages;
    }));
  });
};

exports.block = function (req, res, next) {
  var loginname = req.params.name;
  var action = req.body.action;

  var ep = EventProxy.create();
  ep.fail(next);

  User.getUserByLoginName(loginname, ep.done(function (user) {
    if (!user) {
      return next(new Error('user is not exists'));
    }
    if (action === 'set_block') {
      ep.all('block_user', 'del_topics', 'del_replys',
        function (user, topics, replys) {
          res.json({status: 'success'});
        });
      user.is_block = true;
      user.save(ep.done('block_user'));

      // 防止误操作，平时都注释
      // TopicModel.remove({author_id: user._id}, ep.done('del_topics'));
      // ReplyModel.remove({author_id: user._id}, ep.done('del_replys'));
      ep.emit('del_topics');
      ep.emit('del_replys');
      // END 防止误操作，平时都注释

    } else if (action === 'cancel_block') {
      user.is_block = false;
      user.save(ep.done(function () {

        res.json({status: 'success'});
      }));
    }
  }));
};
<commit_msg>little fix<commit_after>
var User = require('../proxy').User;
var Topic = require('../proxy').Topic;
var Reply = require('../proxy').Reply;
var TopicCollect = require('../proxy').TopicCollect;
var utility = require('utility');
var util = require('util');
var TopicModel = require('../model/topic');
var ReplyModel = require('../model/reply');

var message = require('../common/message');
var tools = require('../common/tools');
var config = require('../config');
var EventProxy = require('eventproxy');
var validator = require('validator');
var utility = require('utility');
var _ = require('lodash');

exports.index = function (req, res, next) {
  var user_name = req.params.name;
  User.getUserByLoginName(user_name, function (err, user) {
    if (err) {
      return next(err);
    }
    if (!user) {
      res.render('notify/notify', {error: '这个用户不存在。'});
      return;
    }

    var render = function (recent_topics, recent_replies) {
      user.friendly_create_at = tools.formatDate(user.create_at, true);
      // 如果用户没有激活，那么管理员可以帮忙激活
      var token = '';
      if (!user.active && req.session.user && req.session.user.is_admin) {
        token = utility.md5(user.email + user.pass + config.session_secret);
      }
      res.render('user/index', {
        user: user,
        recent_topics: recent_topics,
        recent_replies: recent_replies,
        token: token,
        pageTitle: util.format('@%s 的个人主页', user.loginname),
      });
    };

    var proxy = new EventProxy();
    proxy.assign('recent_topics', 'recent_replies', render);
    proxy.fail(next);

    var query = {author_id: user._id};
    var opt = {limit: 5, sort: '-create_at'};
    Topic.getTopicsByQuery(query, opt, proxy.done('recent_topics'));

    Reply.getRepliesByAuthorId(user._id, {limit: 20, sort: '-create_at'},
      proxy.done(function (replies) {
        var topic_ids = [];
        for (var i = 0; i < replies.length; i++) {
          if (topic_ids.indexOf(replies[i].topic_id.toString()) < 0) {
            topic_ids.push(replies[i].topic_id.toString());
          }
        }
        var query = {_id: {'$in': topic_ids}};
        var opt = {limit: 5, sort: '-create_at'};
        Topic.getTopicsByQuery(query, opt, proxy.done('recent_replies'));
      }));
  });
};

exports.show_stars = function (req, res, next) {
  User.getUsersByQuery({is_star: true}, {}, function (err, stars) {
    if (err) {
      return next(err);
    }
    res.render('user/stars', {stars: stars});
  });
};

exports.showSetting = function (req, res, next) {
  User.getUserById(req.session.user._id, function (err, user) {
    if (err) {
      return next(err);
    }
    if (req.query.save === 'success') {
      user.success = '保存成功。';
    }
    user.error = null;
    return res.render('user/setting', user);
  });
};

exports.setting = function (req, res, next) {
  var ep = new EventProxy();
  ep.fail(next);

  // 显示出错或成功信息
  function showMessage(msg, data, isSuccess) {
    data = data || req.body;
    var data2 = {
      loginname: data.loginname,
      email: data.email,
      url: data.url,
      location: data.location,
      signature: data.signature,
      weibo: data.weibo,
    };
    if (isSuccess) {
      data2.success = msg;
    } else {
      data2.error = msg;
    }
    res.render('user/setting', data2);
  }

  // post
  var action = req.body.action;
  if (action === 'change_setting') {
    var url = validator.trim(req.body.url);
    url = validator.escape(url);
    var location = validator.trim(req.body.location);
    location = validator.escape(location);
    var weibo = validator.trim(req.body.weibo);
    weibo = validator.escape(weibo);
    var signature = validator.trim(req.body.signature);
    signature = validator.escape(signature);

    User.getUserById(req.session.user._id, ep.done(function (user) {
      user.url = url;
      user.location = location;
      user.signature = signature;
      user.weibo = weibo;
      // create gravatar
      user.avatar = User.makeGravatar(user.email);
      user.save(function (err) {
        if (err) {
          return next(err);
        }
        req.session.user = user.toObject({virtual: true});
        return res.redirect('/setting?save=success');
      });
    }));
  }
  if (action === 'change_password') {
    var old_pass = validator.trim(req.body.old_pass);
    var new_pass = validator.trim(req.body.new_pass);
    if (!old_pass || !new_pass) {
      return res.send('旧密码或新密码不得为空');
    }

    User.getUserById(req.session.user._id, ep.done(function (user) {
      tools.bcompare(old_pass, user.pass, ep.done(function (bool) {
        if (!bool) {
          return showMessage('当前密码不正确。', user);
        }

        tools.bhash(new_pass, ep.done(function (passhash) {
          user.pass = passhash;
          user.save(function (err) {
            if (err) {
              return next(err);
            }
            return showMessage('密码已被修改。', user, true);

          });
        }));
      }));
    }));
  }
};

exports.toggle_star = function (req, res, next) {
  var user_id = req.body.user_id;
  User.getUserById(user_id, function (err, user) {
    if (err) {
      return next(err);
    }
    if (!user) {
      return next(new Error('user is not exists'));
    }
    user.is_star = !user.is_star;
    user.save(function (err) {
      if (err) {
        return next(err);
      }
      res.json({ status: 'success' });
    });
  });
};

exports.get_collect_topics = function (req, res, next) {
  var name = req.params.name;
  User.getUserByLoginName(name, function (err, user) {
    if (err || !user) {
      return next(err);
    }

    var page = Number(req.query.page) || 1;
    var limit = config.list_topic_count;

    var render = function (topics, pages) {
      res.render('user/collect_topics', {
        topics: topics,
        current_page: page,
        pages: pages,
        user: user
      });
    };

    var proxy = EventProxy.create('topics', 'pages', render);
    proxy.fail(next);

    TopicCollect.getTopicCollectsByUserId(user._id, proxy.done(function (docs) {
      var ids = [];
      for (var i = 0; i < docs.length; i++) {
        ids.push(docs[i].topic_id);
      }
      var query = { _id: { '$in': ids } };
      var opt = {
        skip: (page - 1) * limit,
        limit: limit,
        sort: '-create_at'
      };
      Topic.getTopicsByQuery(query, opt, proxy.done('topics'));
      Topic.getCountByQuery(query, proxy.done(function (all_topics_count) {
        var pages = Math.ceil(all_topics_count / limit);
        proxy.emit('pages', pages);
      }));
    }));
  });
};

exports.top100 = function (req, res, next) {
  var opt = {limit: 100, sort: '-score'};
  User.getUsersByQuery({'$or': [
    {is_block: {'$exists': false}},
    {is_block: false},
  ]}, opt, function (err, tops) {
    if (err) {
      return next(err);
    }
    res.render('user/top100', {
      users: tops,
      pageTitle: 'top100',
    });
  });
};

exports.list_topics = function (req, res, next) {
  var user_name = req.params.name;
  var page = Number(req.query.page) || 1;
  var limit = config.list_topic_count;

  User.getUserByLoginName(user_name, function (err, user) {
    if (!user) {
      res.render('notify/notify', {error: '这个用户不存在。'});
      return;
    }

    var render = function (topics, pages) {
      user.friendly_create_at = tools.formatDate(user.create_at, true);
      res.render('user/topics', {
        user: user,
        topics: topics,
        current_page: page,
        pages: pages
      });
    };

    var proxy = new EventProxy();
    proxy.assign('topics', 'pages', render);
    proxy.fail(next);

    var query = {'author_id': user._id};
    var opt = {skip: (page - 1) * limit, limit: limit, sort: '-create_at'};
    Topic.getTopicsByQuery(query, opt, proxy.done('topics'));

    Topic.getCountByQuery(query, proxy.done(function (all_topics_count) {
      var pages = Math.ceil(all_topics_count / limit);
      proxy.emit('pages', pages);
    }));
  });
};

exports.list_replies = function (req, res, next) {
  var user_name = req.params.name;
  var page = Number(req.query.page) || 1;
  var limit = 50;

  User.getUserByLoginName(user_name, function (err, user) {
    if (!user) {
      res.render('notify/notify', {error: '这个用户不存在。'});
      return;
    }

    var render = function (topics, pages) {
      user.friendly_create_at = tools.formatDate(user.create_at, true);
      res.render('user/replies', {
        user: user,
        topics: topics,
        current_page: page,
        pages: pages
      });
    };

    var proxy = new EventProxy();
    proxy.assign('topics', 'pages', render);
    proxy.fail(next);

    var opt = {skip: (page - 1) * limit, limit: limit, sort: '-create_at'};
    Reply.getRepliesByAuthorId(user._id, opt, proxy.done(function (replies) {
      // 获取所有有评论的主题
      var topic_ids = replies.map(function (reply) {
        return reply.topic_id;
      });
      topic_ids = _.uniq(topic_ids);
      var query = {'_id': {'$in': topic_ids}};
      Topic.getTopicsByQuery(query, {}, proxy.done('topics'));
    }));

    Reply.getCountByAuthorId(user._id, proxy.done('pages', function (count) {
      var pages = Math.ceil(count / limit);
      return pages;
    }));
  });
};

exports.block = function (req, res, next) {
  var loginname = req.params.name;
  var action = req.body.action;

  var ep = EventProxy.create();
  ep.fail(next);

  User.getUserByLoginName(loginname, ep.done(function (user) {
    if (!user) {
      return next(new Error('user is not exists'));
    }
    if (action === 'set_block') {
      ep.all('block_user', 'del_topics', 'del_replys',
        function (user, topics, replys) {
          res.json({status: 'success'});
        });
      user.is_block = true;
      user.save(ep.done('block_user'));

      // 防止误操作，平时都注释
      // TopicModel.remove({author_id: user._id}, ep.done('del_topics'));
      // ReplyModel.remove({author_id: user._id}, ep.done('del_replys'));
      ep.emit('del_topics');
      ep.emit('del_replys');
      // END 防止误操作，平时都注释

    } else if (action === 'cancel_block') {
      user.is_block = false;
      user.save(ep.done(function () {

        res.json({status: 'success'});
      }));
    }
  }));
};
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * The navigation bar at the bottom of the screen.
 * For a single-screen sim, it shows the name of the sim at the far left and the PhET button at the far right.
 * For a multi-screen sim, it additionally shows buttons for each screen, and a home button.
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var HomeButton = require( 'JOIST/HomeButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetButton = require( 'JOIST/PhetButton' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );

  // constants
  var TITLE_LEFT_MARGIN = 10;
  var TITLE_RIGHT_MARGIN = 20;
  var PHET_BUTTON_LEFT_MARGIN = TITLE_RIGHT_MARGIN;
  var HOME_BUTTON_LEFT_MARGIN = 20;
  var SCREEN_BUTTON_SPACING = 0;

  /**
   * Creates a nav bar.
   * @param {Dimension2} barSize initial dimensions of the navigation bar
   * @param {Sim} sim
   * @param {Screen[]} screens
   * @param {Object} [options]
   * @constructor
   */
  function NavigationBar( barSize, sim, screens, options ) {

    options = _.extend( {
      tandem: null
    }, options );

    //TODO #263 this requirement is disabled until we decide how to address it, since several sims violate it
    //if ( screens.length > 1 ) {
    //  assert && assert( _.findIndex( screens, function( screen ) {
    //      return ( screen.navigationBarIcon.width !== screens[ 0 ].navigationBarIcon.width ) || ( screen.navigationBarIcon.height !== screens[ 0 ].navigationBarIcon.height );
    //    } ) === -1,
    //    'all navigation bar icons must have the same size' );
    //}

    this.screens = screens;

    Node.call( this );

    // The bar's background
    this.background = new Rectangle( 0, 0, barSize.width, barSize.height );
    sim.lookAndFeel.navigationBarFillProperty.linkAttribute( this.background, 'fill' );
    this.addChild( this.background );

    // Sim title
    var title = new Text( sim.name, {
      font: new PhetFont( 18 )
    } );
    sim.lookAndFeel.navigationBarTextFillProperty.linkAttribute( title, 'fill' );
    this.titleParent = new Node( { children: [ title ] } ); // wrap in a parent node, so we can scale the parent without affecting title.maxWidth
    this.addChild( this.titleParent );

    // PhET button
    this.phetButton = new PhetButton( sim, sim.lookAndFeel.navigationBarFillProperty, sim.lookAndFeel.navigationBarTextFillProperty, {
      tandem: options.tandem ? options.tandem.createTandem( 'phetButton' ) : null
    } );
    this.addChild( this.phetButton );

    if ( screens.length === 1 ) {
      /* single-screen sim */

      // title can occupy all space to the left of the PhET button
      title.maxWidth = this.background.width - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN - this.phetButton.width - PhetButton.HORIZONTAL_INSET;
    }
    else {
      /* multi-screen sim */

      // title can occupy this percentage of the bar
      title.maxWidth = 0.3 * this.background.width;

      // Create the home button
      this.homeButton = new HomeButton( barSize.height, sim.lookAndFeel.navigationBarFillProperty, {
        listener: function() {
          sim.showHomeScreen = true;
        }
      } );
      this.addChild( this.homeButton );

      // Allocate remaining horizontal space equally for screen buttons, assuming they will be centered in the navbar.
      // available width left of center
      var availableLeft = ( this.background.width / 2 ) - TITLE_LEFT_MARGIN - this.titleParent.width - TITLE_RIGHT_MARGIN;
      // available width right of center
      var availableRight = ( this.background.width / 2 ) - HOME_BUTTON_LEFT_MARGIN - this.homeButton.width - PHET_BUTTON_LEFT_MARGIN - this.phetButton.width - PhetButton.HORIZONTAL_INSET;
      // total available width for the screen buttons when they are centered
      var availableTotal = 2 * Math.min( availableLeft, availableRight );
      // width per screen button
      var screenButtonWidth = ( availableTotal / screens.length ) - ( ( screens.length - 1 ) * SCREEN_BUTTON_SPACING );

      var screenButtons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton(
          sim.lookAndFeel.navigationBarFillProperty,
          sim.screenIndexProperty,
          sim.screens,
          screen,
          barSize.height, {
            maxTextWidth: screenButtonWidth, //TODO #241 this is closer, but not quite right, since it doesn't account for the button highlight
            tandem: options.tandem && options.tandem.createTandem( screen.tandemScreenName + 'Button' )
          } );
      } );

      // Layout out screen buttons horizontally, with equal distance between their centers
      var maxScreenButtonWidth = Math.max( 50, _.max( screenButtons, function( button ) { return button.width; } ).width );
      for ( var i = 1; i < screenButtons.length; i++ ) {
        screenButtons[ i ].centerX = screenButtons[ i - 1 ].centerX + maxScreenButtonWidth + SCREEN_BUTTON_SPACING;
      }

      // Put all screen buttons under a parent, to simplify layout
      this.screenButtonsParent = new Node( {
        children: screenButtons,
        maxWidth: availableTotal // in case we have so many screens that the screen buttons need to be scaled down
      } );
      this.addChild( this.screenButtonsParent );
    }

    this.layout( 1, barSize.width, barSize.height );
  }

  return inherit( Node, NavigationBar, {

    /**
     * Called when the navigation bar layout needs to be updated, typically when the browser window is resized.
     * @param {number} scale
     * @param {number} width
     * @param {number} height
     * @public
     */
    layout: function( scale, width, height ) {

      // resize the background
      this.background.rectWidth = width;
      this.background.rectHeight = height;

      // title at left end
      this.titleParent.setScaleMagnitude( scale );
      this.titleParent.left = this.background.left + TITLE_LEFT_MARGIN;
      this.titleParent.centerY = this.background.centerY;

      // PhET button at right end
      this.phetButton.setScaleMagnitude( scale );
      this.phetButton.right = width - PhetButton.HORIZONTAL_INSET;
      this.phetButton.bottom = height - PhetButton.VERTICAL_INSET;

      // For multi-screen sims ...
      if ( this.screens.length !== 1 ) {

        // screen buttons centered
        this.screenButtonsParent.setScaleMagnitude( scale );
        this.screenButtonsParent.center = this.background.center;

        // home button to the right of screen buttons
        this.homeButton.setScaleMagnitude( scale );
        this.homeButton.left = this.screenButtonsParent.right + HOME_BUTTON_LEFT_MARGIN;
        this.homeButton.centerY = this.background.centerY;
      }
    }
  } );
} );<commit_msg>#241 remove magic number in computation of maxScreenButtonWidth, increase SCREEN_BUTTON_SPACING<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * The navigation bar at the bottom of the screen.
 * For a single-screen sim, it shows the name of the sim at the far left and the PhET button at the far right.
 * For a multi-screen sim, it additionally shows buttons for each screen, and a home button.
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var HomeButton = require( 'JOIST/HomeButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetButton = require( 'JOIST/PhetButton' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );

  // constants
  var TITLE_LEFT_MARGIN = 10;
  var TITLE_RIGHT_MARGIN = 20;
  var PHET_BUTTON_LEFT_MARGIN = TITLE_RIGHT_MARGIN;
  var HOME_BUTTON_LEFT_MARGIN = 20;
  var SCREEN_BUTTON_SPACING = 10;

  /**
   * Creates a nav bar.
   * @param {Dimension2} barSize initial dimensions of the navigation bar
   * @param {Sim} sim
   * @param {Screen[]} screens
   * @param {Object} [options]
   * @constructor
   */
  function NavigationBar( barSize, sim, screens, options ) {

    options = _.extend( {
      tandem: null
    }, options );

    //TODO #263 this requirement is disabled until we decide how to address it, since several sims violate it
    //if ( screens.length > 1 ) {
    //  assert && assert( _.findIndex( screens, function( screen ) {
    //      return ( screen.navigationBarIcon.width !== screens[ 0 ].navigationBarIcon.width ) || ( screen.navigationBarIcon.height !== screens[ 0 ].navigationBarIcon.height );
    //    } ) === -1,
    //    'all navigation bar icons must have the same size' );
    //}

    this.screens = screens;

    Node.call( this );

    // The bar's background
    this.background = new Rectangle( 0, 0, barSize.width, barSize.height );
    sim.lookAndFeel.navigationBarFillProperty.linkAttribute( this.background, 'fill' );
    this.addChild( this.background );

    // Sim title
    var title = new Text( sim.name, {
      font: new PhetFont( 18 )
    } );
    sim.lookAndFeel.navigationBarTextFillProperty.linkAttribute( title, 'fill' );
    this.titleParent = new Node( { children: [ title ] } ); // wrap in a parent node, so we can scale the parent without affecting title.maxWidth
    this.addChild( this.titleParent );

    // PhET button
    this.phetButton = new PhetButton( sim, sim.lookAndFeel.navigationBarFillProperty, sim.lookAndFeel.navigationBarTextFillProperty, {
      tandem: options.tandem ? options.tandem.createTandem( 'phetButton' ) : null
    } );
    this.addChild( this.phetButton );

    if ( screens.length === 1 ) {
      /* single-screen sim */

      // title can occupy all space to the left of the PhET button
      title.maxWidth = this.background.width - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN - this.phetButton.width - PhetButton.HORIZONTAL_INSET;
    }
    else {
      /* multi-screen sim */

      // title can occupy this percentage of the bar
      title.maxWidth = 0.3 * this.background.width;

      // Create the home button
      this.homeButton = new HomeButton( barSize.height, sim.lookAndFeel.navigationBarFillProperty, {
        listener: function() {
          sim.showHomeScreen = true;
        }
      } );
      this.addChild( this.homeButton );

      // Allocate remaining horizontal space equally for screen buttons, assuming they will be centered in the navbar.
      // available width left of center
      var availableLeft = ( this.background.width / 2 ) - TITLE_LEFT_MARGIN - this.titleParent.width - TITLE_RIGHT_MARGIN;
      // available width right of center
      var availableRight = ( this.background.width / 2 ) - HOME_BUTTON_LEFT_MARGIN - this.homeButton.width - PHET_BUTTON_LEFT_MARGIN - this.phetButton.width - PhetButton.HORIZONTAL_INSET;
      // total available width for the screen buttons when they are centered
      var availableTotal = 2 * Math.min( availableLeft, availableRight );
      // width per screen button
      var screenButtonWidth = ( availableTotal / screens.length ) - ( ( screens.length - 1 ) * SCREEN_BUTTON_SPACING );

      var screenButtons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton(
          sim.lookAndFeel.navigationBarFillProperty,
          sim.screenIndexProperty,
          sim.screens,
          screen,
          barSize.height, {
            maxTextWidth: screenButtonWidth, //TODO #241 this is closer, but not quite right, since it doesn't account for the button highlight
            tandem: options.tandem && options.tandem.createTandem( screen.tandemScreenName + 'Button' )
          } );
      } );

      // Layout out screen buttons horizontally, with equal distance between their centers
      var maxScreenButtonWidth = _.max( screenButtons, function( button ) { return button.width; } ).width;
      for ( var i = 1; i < screenButtons.length; i++ ) {
        screenButtons[ i ].centerX = screenButtons[ i - 1 ].centerX + maxScreenButtonWidth + SCREEN_BUTTON_SPACING;
      }

      // Put all screen buttons under a parent, to simplify layout
      this.screenButtonsParent = new Node( {
        children: screenButtons,
        maxWidth: availableTotal // in case we have so many screens that the screen buttons need to be scaled down
      } );
      this.addChild( this.screenButtonsParent );
    }

    this.layout( 1, barSize.width, barSize.height );
  }

  return inherit( Node, NavigationBar, {

    /**
     * Called when the navigation bar layout needs to be updated, typically when the browser window is resized.
     * @param {number} scale
     * @param {number} width
     * @param {number} height
     * @public
     */
    layout: function( scale, width, height ) {

      // resize the background
      this.background.rectWidth = width;
      this.background.rectHeight = height;

      // title at left end
      this.titleParent.setScaleMagnitude( scale );
      this.titleParent.left = this.background.left + TITLE_LEFT_MARGIN;
      this.titleParent.centerY = this.background.centerY;

      // PhET button at right end
      this.phetButton.setScaleMagnitude( scale );
      this.phetButton.right = width - PhetButton.HORIZONTAL_INSET;
      this.phetButton.bottom = height - PhetButton.VERTICAL_INSET;

      // For multi-screen sims ...
      if ( this.screens.length !== 1 ) {

        // screen buttons centered
        this.screenButtonsParent.setScaleMagnitude( scale );
        this.screenButtonsParent.center = this.background.center;

        // home button to the right of screen buttons
        this.homeButton.setScaleMagnitude( scale );
        this.homeButton.left = this.screenButtonsParent.right + HOME_BUTTON_LEFT_MARGIN;
        this.homeButton.centerY = this.background.centerY;
      }
    }
  } );
} );<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var express = require('express')
  , restrict = require('lib/utils').restrict
  , api = require('lib/db-api')
  , log = require('debug')('democracyos:citizen');

var app = module.exports = express();

app.get('/citizen/all', restrict, function (req, res) {
  log('Request /citizen/all');

  api.citizen.all(function(err, citizens) {
    if (err) return _handleError(err, req, res);

    log('Serving citizens %j', citizens);
    res.json(citizens);
  });
});

app.get('/citizen/me', restrict, function (req, res) {
  log('Request /citizen/me');

  log('Serving citizen %j', req.user);
  res.json(req.user);

});

app.get('/citizen/search', restrict, function (req, res) {
  var q = req.param('q');

  log('Request /citizen/search %j', q);

  api.citizen.search(q, function(err, citizens) {
    if (err) return _handleError(err, req, res);

    log('Serving citizens %j', citizens);
    res.json(citizens);
  })
});

app.get('/citizen/lookup', function (req, res) {
  var ids = req.param('ids');

  log('Request /citizen/lookup %j', ids);

  if (!ids) return log('Cannot process without ids'), res.json(500,{});

  api.citizen.lookup(ids.split(','), function(err, citizens) {
    if (err) return _handleError(err, req, res);

    log('Serving citizens %j', citizens);
    res.json(citizens);
  })
});

app.get('/citizen/:id', restrict, function (req, res) {
  log('Request /citizen/%s', req.params.id);

  api.citizen.get(req.params.id, function (err, citizen) {
    if (err) return _handleError(err, req, res);

    log('Serving citizen %j', citizen);
    res.json(citizen);
  });
});

function _handleError (err, req, res) {
  res.format({
    html: function() {
      // this should be handled better!
      // maybe with flash or even an
      // error page.
      log('Error found with html request %j', err);
      res.redirect('back');
    },
    json: function() {
      log("Error found: %j", err);
      res.json({ error: err });
    }
  })
}<commit_msg>[citizen:endpoint] - Add post /citizen/lookup method for 414 Request-URI Too Large. #155<commit_after>/**
 * Module dependencies.
 */

var express = require('express')
  , restrict = require('lib/utils').restrict
  , api = require('lib/db-api')
  , log = require('debug')('democracyos:citizen');

var app = module.exports = express();

app.get('/citizen/all', restrict, function (req, res) {
  log('Request /citizen/all');

  api.citizen.all(function(err, citizens) {
    if (err) return _handleError(err, req, res);

    log('Serving citizens %j', citizens);
    res.json(citizens);
  });
});

app.get('/citizen/me', restrict, function (req, res) {
  log('Request /citizen/me');

  log('Serving citizen %j', req.user);
  res.json(req.user);

});

app.get('/citizen/search', restrict, function (req, res) {
  var q = req.param('q');

  log('Request /citizen/search %j', q);

  api.citizen.search(q, function(err, citizens) {
    if (err) return _handleError(err, req, res);

    log('Serving citizens %j', citizens);
    res.json(citizens);
  })
});

app.get('/citizen/lookup', function (req, res) {
  var ids = req.param('ids');

  log('Request /citizen/lookup %j', ids);

  if (!ids) return log('Cannot process without ids'), res.json(500,{});

  api.citizen.lookup(ids.split(','), function(err, citizens) {
    if (err) return _handleError(err, req, res);

    log('Serving citizens %j', citizens);
    res.json(citizens);
  })
});

// This is a temporary hack while lookinf for a better solution to
// this error: 414 Request-URI Too Large
app.post('/citizen/lookup', function (req, res) {
  var ids = req.param('ids');

  log('Request /citizen/lookup %j', ids);

  if (!ids) return log('Cannot process without ids'), res.json(500,{});

  api.citizen.lookup(ids, function(err, citizens) {
    if (err) return _handleError(err, req, res);

    log('Serving citizens %j', citizens);
    res.json(citizens);
  })
});

app.get('/citizen/:id', restrict, function (req, res) {
  log('Request /citizen/%s', req.params.id);

  api.citizen.get(req.params.id, function (err, citizen) {
    if (err) return _handleError(err, req, res);

    log('Serving citizen %j', citizen);
    res.json(citizen);
  });
});

function _handleError (err, req, res) {
  res.format({
    html: function() {
      // this should be handled better!
      // maybe with flash or even an
      // error page.
      log('Error found with html request %j', err);
      res.redirect('back');
    },
    json: function() {
      log("Error found: %j", err);
      res.json({ error: err });
    }
  })
}<|endoftext|>