<commit_before>// Dependencies
var parser = require('cheerio');
var entities = require('entities');
var Promise = require('bluebird');

// Includes
var http = require('./util/http.js').func;
var getForumError = require('./util/getForumError.js').func;
var getDate = require('./util/getDate.js').func;

// Args
exports.required = ['postId'];
exports.optional = ['page'];

// Define
var debug = false;
var maxPageSize = 25;

function doPage (thread, postId, pageNumber, target, tries) {
  if (debug) {
    console.log('Do page ' + pageNumber + ' of ' + postId + ' for ' + JSON.stringify(target) + ' out of ' + (thread && thread.totalPages));
  }
  var httpOpt = {
    url: '//forum.roblox.com/Forum/ShowPost.aspx?PostID=' + postId + '&PageIndex=' + pageNumber,
    options: {
      resolveWithFullResponse: true,
      followRedirect: false
    }
  };
  return http(httpOpt)
  .then(function (res) {
    if (res.statusCode === 302) {
      throw getForumError({location: res.headers.location, append: 'Could not get forum post page ' + pageNumber});
    } else {
      var body = res.body;
      var $ = parser.load(body);
      var posts = $('.forum-post');
      var total = thread && thread.totalPages;
      var exists = total && total > 0;
      var last;
      if (thread && !thread.subject) {
        thread.subject = $('#ctl00_cphRoblox_PostView1_ctl00_PostTitle').text();
      }
      if (!thread || !exists) {
        var totalPages = parseInt($('#ctl00_cphRoblox_PostView1_ctl00_Pager').find('.normalTextSmallBold').eq(0).text().match(/\d+$/)[0], 10);
        if (!thread) {
          return totalPages;
        } else {
          thread.totalPages = totalPages;
        }
      }
      for (var i = 0; i < posts.length; i++) {
        var post = posts.eq(i);
        var tag = post.find('.normalTextSmaller').eq(3);
        var id = parseInt(tag.find('a').attr('name'), 10);
        last = id;
        if (pageNumber === 1 && i === 0 && id !== postId) {
          target = {
            min: 1,
            max: 0,
            id: postId
          };
          postId = id;
          thread.totalPages = -1;
        } else if (!target || id === target.id) {
          var content = entities.decodeHTML(post.find('.normalTextSmall').html()).replace(/
/g, '\n').replace(/<.*?>/g, ''); // Replace breaks with newlines and then remove remaining html tags (because URLs in particular are left inside)
          var info = post.find('table').eq(0);
          var span = info.find('span');
          var join = getDate({time: span.eq(0).text().substring(8), timezone: 'CT'});
          var postCount = parseInt(span.eq(1).text().substring(13), 10);
          var user = post.find('.normalTextSmallBold');
          var userId = parseInt(user.attr('href').match(/\d+/)[0], 10);
          var date = getDate({time: tag.text().substring(8), timezone: 'CT'}); // Source dates are in CT, when viewed in a browser they are converted to local time: this was kind of confusing
          thread.posts.push({
            author: {
              id: userId,
              name: user.text(),
              joinDate: join,
              postCount: postCount
            },
            postId: id,
            date: date,
            content: content,
            parent: {id: postId, page: pageNumber}
          });
        }
      }
      if (target && thread.posts.length === 0) {
        var min = target.min;
        var max = target.max;
        if (max === -1) {
          max = thread.totalPages;
        }
        if (min === max) {
          throw new Error('Could not find reply in thread'); // This should never happen, but who knows with ROBLOX
        }
        if (thread.totalPages < 0) {
          target.max = -1;
          return doPage(thread, postId, 1, target);
        }
        if (last < target.id) {
          min = pageNumber + 1;
        } else {
          max = pageNumber - 1;
        }
        target.min = min;
        target.max = max;
        if (thread.totalPages) {
          return doPage(thread, postId, Math.floor((min + max) / 2), target);
        }
      }
      if (target) {
        thread.resolved = postId;
      }
    }
  })
  .catch(function (err) {
    console.error(err.stack);
    if (!tries || tries < 3) {
      return getPage(thread, postId, pageNumber, target, tries ? tries + 1 : 0);
    }
  });
}

function getPage (thread, postId, pageNumber, target) {
  return doPage(thread, postId, pageNumber, target);
}

function getPages (thread, postId, pages, totalPages) {
  var jobs = [];
  var total = totalPages;
  var start, end;
  var isArray = pages instanceof Array;
  if (isArray) {
    start = 0;
    end = pages.length;
  } else {
    start = 2;
    end = pages + 1;
  }
  for (var i = start; i < end; i++) {
    var page = isArray ? pages[i] : i;
    if (page < 0) {
      page = total + 1 + page;
    }
    if (!totalPages || (page <= totalPages && page > 0)) {
      jobs.push(getPage(thread, postId, page));
    }
  }
  return Promise.all(jobs)
  .then(function () {
    thread.posts = thread.posts.sort(function (a, b) {
      return a.postId - b.postId;
    });
    return thread;
  });
}

exports.func = function (args) {
  var page = args.page;
  var postId = args.postId;
  var isArray = page instanceof Array;
  var thread = {
    posts: []
  };
  var promise;
  if (page) {
    if (isArray) {
      page = page.sort(function (a, b) {
        return a - b;
      });
    } else {
      page = [page];
    }
    var low = page[0];
    if (low < 0) {
      // All this logic is to minimize the number of requests and get page number from a request that already has to be made, if possible
      var high = page[page.length - 1];
      var replace = high > 0;
      promise = getPage(replace ? thread : null, postId, replace ? high : 1)
      .then(function (total) {
        if (replace) {
          page.pop();
          total = thread.totalPages;
        }
        return getPages(thread, postId, page, total);
      });
    } else {
      promise = getPages(thread, postId, page);
    }
  } else {
    promise = getPage(thread, postId, 1)
    .then(function () {
      if (thread.resolved) {
        return thread;
      }
      var total = thread.totalPages;
      return getPages(thread, postId, total, total);
    });
  }
  promise.getStatus = function () {
    return Math.round((thread.totalPages ? (thread.posts.length / maxPageSize) / thread.totalPages : 0) * 10000) / 100;
  };
  return promise;
};
<commit_msg>Add online and tag<commit_after>// Dependencies
var parser = require('cheerio');
var entities = require('entities');
var Promise = require('bluebird');

// Includes
var http = require('./util/http.js').func;
var getForumError = require('./util/getForumError.js').func;
var getDate = require('./util/getDate.js').func;

// Args
exports.required = ['postId'];
exports.optional = ['page'];

// Define
var debug = false;
var maxPageSize = 25;

function doPage (thread, postId, pageNumber, target, tries) {
  if (debug) {
    console.log('Do page ' + pageNumber + ' of ' + postId + ' for ' + JSON.stringify(target) + ' out of ' + (thread && thread.totalPages));
  }
  var httpOpt = {
    url: '//forum.roblox.com/Forum/ShowPost.aspx?PostID=' + postId + '&PageIndex=' + pageNumber,
    options: {
      resolveWithFullResponse: true,
      followRedirect: false
    }
  };
  return http(httpOpt)
  .then(function (res) {
    if (res.statusCode === 302) {
      throw getForumError({location: res.headers.location, append: 'Could not get forum post page ' + pageNumber});
    } else {
      var body = res.body;
      var $ = parser.load(body);
      var posts = $('.forum-post');
      var total = thread && thread.totalPages;
      var exists = total && total > 0;
      var last;
      if (thread && !thread.subject) {
        thread.subject = $('#ctl00_cphRoblox_PostView1_ctl00_PostTitle').text();
      }
      if (!thread || !exists) {
        var totalPages = parseInt($('#ctl00_cphRoblox_PostView1_ctl00_Pager').find('.normalTextSmallBold').eq(0).text().match(/\d+$/)[0], 10);
        if (!thread) {
          return totalPages;
        } else {
          thread.totalPages = totalPages;
        }
      }
      for (var i = 0; i < posts.length; i++) {
        var post = posts.eq(i);
        var meta = post.find('.normalTextSmaller').eq(3);
        var id = parseInt(meta.find('a').attr('name'), 10);
        last = id;
        if (pageNumber === 1 && i === 0 && id !== postId) {
          target = {
            min: 1,
            max: 0,
            id: postId
          };
          postId = id;
          thread.totalPages = -1;
        } else if (!target || id === target.id) {
          var content = entities.decodeHTML(post.find('.normalTextSmall').html()).replace(/
/g, '\n').replace(/<.*?>/g, ''); // Replace breaks with newlines and then remove remaining html tags (because URLs in particular are left inside)
          var info = post.find('table').eq(0);
          var span = info.find('span');
          var join = getDate({time: span.eq(0).text().substring(8), timezone: 'CT'});
          var postCount = parseInt(span.eq(1).text().substring(13), 10);
          var user = post.find('.normalTextSmallBold');
          var userId = parseInt(user.attr('href').match(/\d+/)[0], 10);
          var date = getDate({time: meta.text(), timezone: 'CT'}); // Source dates are in CT, when viewed in a browser they are converted to local time: this was kind of confusing
          var img = post.find('img[alt]');
          var online = img.eq(0).attr('src') === '/Forum/skins/default/images/user_IsOnline.gif';
          var tag = img.length > 1 && img.eq(1).attr('alt');
          thread.posts.push({
            author: {
              id: userId,
              name: user.text(),
              online: online,
              tag: tag,
              joinDate: join,
              postCount: postCount
            },
            postId: id,
            date: date,
            content: content,
            parent: {id: postId, page: pageNumber}
          });
        }
      }
      if (target && thread.posts.length === 0) {
        var min = target.min;
        var max = target.max;
        if (max === -1) {
          max = thread.totalPages;
        }
        if (min === max) {
          throw new Error('Could not find reply in thread'); // This should never happen, but who knows with ROBLOX
        }
        if (thread.totalPages < 0) {
          target.max = -1;
          return doPage(thread, postId, 1, target);
        }
        if (last < target.id) {
          min = pageNumber + 1;
        } else {
          max = pageNumber - 1;
        }
        target.min = min;
        target.max = max;
        if (thread.totalPages) {
          return doPage(thread, postId, Math.floor((min + max) / 2), target);
        }
      }
      if (target) {
        thread.resolved = postId;
      }
    }
  })
  .catch(function (err) {
    console.error(err.stack);
    if (!tries || tries < 3) {
      return getPage(thread, postId, pageNumber, target, tries ? tries + 1 : 0);
    }
  });
}

function getPage (thread, postId, pageNumber, target) {
  return doPage(thread, postId, pageNumber, target);
}

function getPages (thread, postId, pages, totalPages) {
  var jobs = [];
  var total = totalPages;
  var start, end;
  var isArray = pages instanceof Array;
  if (isArray) {
    start = 0;
    end = pages.length;
  } else {
    start = 2;
    end = pages + 1;
  }
  for (var i = start; i < end; i++) {
    var page = isArray ? pages[i] : i;
    if (page < 0) {
      page = total + 1 + page;
    }
    if (!totalPages || (page <= totalPages && page > 0)) {
      jobs.push(getPage(thread, postId, page));
    }
  }
  return Promise.all(jobs)
  .then(function () {
    thread.posts = thread.posts.sort(function (a, b) {
      return a.postId - b.postId;
    });
    return thread;
  });
}

exports.func = function (args) {
  var page = args.page;
  var postId = args.postId;
  var isArray = page instanceof Array;
  var thread = {
    posts: []
  };
  var promise;
  if (page) {
    if (isArray) {
      page = page.sort(function (a, b) {
        return a - b;
      });
    } else {
      page = [page];
    }
    var low = page[0];
    if (low < 0) {
      // All this logic is to minimize the number of requests and get page number from a request that already has to be made, if possible
      var high = page[page.length - 1];
      var replace = high > 0;
      promise = getPage(replace ? thread : null, postId, replace ? high : 1)
      .then(function (total) {
        if (replace) {
          page.pop();
          total = thread.totalPages;
        }
        return getPages(thread, postId, page, total);
      });
    } else {
      promise = getPages(thread, postId, page);
    }
  } else {
    promise = getPage(thread, postId, 1)
    .then(function () {
      if (thread.resolved) {
        return thread;
      }
      var total = thread.totalPages;
      return getPages(thread, postId, total, total);
    });
  }
  promise.getStatus = function () {
    return Math.round((thread.totalPages ? (thread.posts.length / maxPageSize) / thread.totalPages : 0) * 10000) / 100;
  };
  return promise;
};
<|endoftext|>


<commit_before>/**
 * @file 遍历指定目录，获得音乐列表
 * @author BenzLeung(https://github.com/BenzLeung)
 * @date 2017/5/4
 * Created by JetBrains PhpStorm.
 *
 * 每位工程师都有保持代码优雅的义务
 * each engineer has a duty to keep the code elegant
 */

module.exports = function (dir, callback) {
    var fs = require('fs');
    const path = require('path');
    var getMusicData = require('./getMusicData');

    var musicList = [];

    dir = path.normalize(dir);

    fs.readdir(dir, function (err, files) {
        var p;
        var i, len = files.length;
        var musicCount = len;
        var isFinish = false;
        for (i = 0; i < len; i ++) {
            p = path.join(dir, files[i]);
            getMusicData(p, function (data) {
                if (data) {
                    var d = {
                        title: data['common']['title'],
                        artist: data['common']['artist'],
                        album: data['common']['album'],
                        format: data['format']['dataformat'],
                        bitrate: data['format']['bitrate'],
                        duration: data['format']['duration'],
                        filename: data['file']
                    };
                    musicList.push(d);
                } else {
                    musicCount --;
                }
                if (musicList.length >= musicCount && !isFinish) {
                    isFinish = true;
                    callback(musicList);
                }
            });

        }
    });
};
<commit_msg>developing...<commit_after>/**
 * @file 遍历指定目录，获得音乐列表
 * @author BenzLeung(https://github.com/BenzLeung)
 * @date 2017/5/4
 * Created by JetBrains PhpStorm.
 *
 * 每位工程师都有保持代码优雅的义务
 * each engineer has a duty to keep the code elegant
 */

module.exports = function (dir, callback) {
    var fs = require('fs');
    const path = require('path');
    var getMusicData = require('./getMusicData');

    var musicList = [];

    dir = path.normalize(dir);

    fs.readdir(dir, function (err, files) {
        var p;
        var i, len = files.length;
        var musicCount = len;
        var isFinish = false;
        for (i = 0; i < len; i ++) {
            p = path.join(dir, files[i]);
            getMusicData(p, function (data) {
                if (data) {
                    var d = {
                        title: data['common']['title'],
                        artist: data['common']['artist'],
                        album: data['common']['album'],
                        format: data['format']['dataformat'],
                        bitrate: data['format']['bitrate'],
                        duration: data['format']['duration'],
                        filename: data['file'],
                        filesize: data['filesize']
                    };
                    musicList.push(d);
                } else {
                    musicCount --;
                }
                if (musicList.length >= musicCount && !isFinish) {
                    isFinish = true;
                    callback(musicList);
                }
            });

        }
    });
};
<|endoftext|>


<commit_before>'use strict';

var GetIntrinsic = require('get-intrinsic');

var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%');
if ($gOPD) {
	try {
		$gOPD([], 'length');
	} catch (e) {
		// IE 8 has a broken gOPD
		$gOPD = null;
	}
}

module.exports = $gOPD;
<commit_msg>[Fix] `getOwnPropertyDescriptor` helper: avoid crashing in IE < 9<commit_after>'use strict';

var GetIntrinsic = require('get-intrinsic');

var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true);
if ($gOPD) {
	try {
		$gOPD([], 'length');
	} catch (e) {
		// IE 8 has a broken gOPD
		$gOPD = null;
	}
}

module.exports = $gOPD;
<|endoftext|>


<commit_before>/*jslint nomen:true*/
/*global define*/
define(function (require) {
    'use strict';

    var CommentComponent,
        $ = require('jquery'),
        _ = require('underscore'),
        __ = require('orotranslation/js/translator'),
        routing = require('routing'),
        tools = require('oroui/js/tools'),
        mediator = require('oroui/js/mediator'),
        BaseComponent = require('oroui/js/app/components/base/component'),
        CommentFromView = require('orocomment/js/app/views/comment-form-view'),
        CommentListView = require('orocomment/js/app/views/comment-list-view'),
        CommentCollection = require('orocomment/js/app/models/comment-collection');

    CommentComponent = BaseComponent.extend({
        listen: {
            'toEdit collection': 'onCommentEdit'
        },

        initialize: function (options) {
            var collectionOptions;

            this.options = options || {};
            collectionOptions = _.pick(this.options, ['relatedEntityId', 'relatedEntityClassName', 'canCreate']);

            this.collection = new CommentCollection([], collectionOptions);

            this.listView = new CommentListView({
                el: options._sourceElement,
                collection: this.collection,
                template: options.listTemplate
            });

            this.formTemplate = options.listTemplate + '-form';
            if (this.options.canCreate) {
                this.addFormView();
            }

            this.collection.fetch();
        },

        onCommentEdit: function (model) {
            this.addFormView(model);
        },

        addFormView: function (model) {
            var formView, parentView;

            parentView = this.listView;
            if (model) {
                parentView = this.listView.getItemView(model);
            } else {
                model = new this.collection.model();
            }

            formView = new CommentFromView({
                template: this.formTemplate,
                el: parentView.$('.form-container'),
                model: model
            });
            parentView.subview('form', formView);

            this.listenTo(formView, 'submit', this.onFormSubmit, this);
            this.listenTo(formView, 'reset', this.onFormReset, this);
        },

        onFormSubmit: function (formView) {
            var model, itemView, options;

            model = formView.model;
            options = formView.fetchAjaxOptions();

            if (model.isNew()) {
                this.collection.add(model, {at: 0});
            } else {
                itemView = this.listView.getItemView(model);
                options.success = function () {
                    itemView.render();
                }
            }

            options.url = model.url();
            model.save(null, options);
        },

        onFormReset: function (formView) {
            var model, itemView;
            model = formView.model;
            if (!model.isNew()) {
                itemView = this.listView.getItemView(model);
                itemView.render();
            }
        }
    });

    return CommentComponent;
});
<commit_msg>BAP-7187: 'Save Comment' button doesn't work if user hasn't changed data<commit_after>/*jslint nomen:true*/
/*global define*/
define(function (require) {
    'use strict';

    var CommentComponent,
        $ = require('jquery'),
        _ = require('underscore'),
        __ = require('orotranslation/js/translator'),
        routing = require('routing'),
        tools = require('oroui/js/tools'),
        mediator = require('oroui/js/mediator'),
        BaseComponent = require('oroui/js/app/components/base/component'),
        CommentFromView = require('orocomment/js/app/views/comment-form-view'),
        CommentListView = require('orocomment/js/app/views/comment-list-view'),
        CommentCollection = require('orocomment/js/app/models/comment-collection');

    CommentComponent = BaseComponent.extend({
        listen: {
            'toEdit collection': 'onCommentEdit'
        },

        initialize: function (options) {
            var collectionOptions;

            this.options = options || {};
            collectionOptions = _.pick(this.options, ['relatedEntityId', 'relatedEntityClassName', 'canCreate']);

            this.collection = new CommentCollection([], collectionOptions);

            this.listView = new CommentListView({
                el: options._sourceElement,
                collection: this.collection,
                template: options.listTemplate
            });

            this.formTemplate = options.listTemplate + '-form';
            if (this.options.canCreate) {
                this.addFormView();
            }

            this.collection.fetch();
        },

        onCommentEdit: function (model) {
            this.addFormView(model);
        },

        addFormView: function (model) {
            var formView, parentView;

            parentView = this.listView;
            if (model) {
                parentView = this.listView.getItemView(model);
            } else {
                model = new this.collection.model();
            }

            formView = new CommentFromView({
                template: this.formTemplate,
                el: parentView.$('.form-container'),
                model: model
            });
            parentView.subview('form', formView);

            this.listenTo(formView, 'submit', this.onFormSubmit, this);
            this.listenTo(formView, 'reset', this.onFormReset, this);
        },

        onFormSubmit: function (formView) {
            var model, listView, options;

            listView = this.listView;
            model = formView.model;

            if (model.isNew()) {
                this.collection.add(model, {at: 0});
            }

            model.once('sync', function () {
                var itemView = listView.getItemView(model);
                if (itemView) {
                    itemView.render();
                }
            });

            options = formView.fetchAjaxOptions({
                url: model.url()
            });
            model.save(null, options);
        },

        onFormReset: function (formView) {
            var model, itemView;
            model = formView.model;
            if (!model.isNew()) {
                itemView = this.listView.getItemView(model);
                itemView.render();
            }
        }
    });

    return CommentComponent;
});
<|endoftext|>


<commit_before>/* global Chart */
import Component from '@ember/component';
import {action} from '@ember/object';
import {computed, get} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';

export default Component.extend({
    ajax: service(),
    membersStats: service(),

    // public attrs
    nightShift: false,

    stats: null,
    chartData: null,
    chartOptions: null,
    startDateLabel: '',

    selectedRange: computed('membersStats.days', function () {
        const availableRanges = this.availableRanges;
        return availableRanges.findBy('days', this.membersStats.days);
    }),

    availableRanges: computed(function () {
        return [{
            name: '30 days',
            days: '30'
        }, {
            name: '90 days',
            days: '90'
        }, {
            name: '365 days',
            days: '365'
        }, {
            name: 'All time',
            days: 'all-time'
        }];
    }),

    // Lifecycle ---------------------------------------------------------------

    init() {
        this._super(...arguments);
        this.setChartJSDefaults();
    },

    didReceiveAttrs() {
        if (this._lastNightShift !== undefined && this.nightShift !== this._lastNightShift) {
            this.setChartOptions();
        }
        this._lastNightShift = this.nightShift;
    },

    // Actions -----------------------------------------------------------------

    changeDateRange: action(function (range) {
        this.membersStats.days = get(range, 'days');
        this.fetchStatsTask.perform();
    }),

    // Tasks -------------------------------------------------------------------

    fetchStatsTask: task(function* () {
        this.set('stats', null);

        let stats = yield this.membersStats.fetch();

        if (stats) {
            this.set('stats', stats);

            this.setChartOptions({
                rangeInDays: Object.keys(stats.total_on_date).length
            });

            this.setChartData({
                dateLabels: Object.keys(stats.total_on_date),
                dateValues: Object.values(stats.total_on_date)
            });
        }
    }),

    // Internal ----------------------------------------------------------------

    setChartData({dateLabels, dateValues}) {
        this.set('chartData', {
            labels: dateLabels,
            datasets: [{
                label: 'Total members',
                cubicInterpolationMode: 'monotone',
                data: dateValues,
                fill: false,
                backgroundColor: 'rgba(62,176,239,.9)',
                pointRadius: 0,
                pointHitRadius: 10,
                borderColor: 'rgba(62,176,239,.9)',
                borderJoinStyle: 'round'
            }]
        });
    },

    setChartOptions({rangeInDays}) {
        let maxTicksAllowed = this.getTicksForRange(rangeInDays);

        this.setChartJSDefaults();

        this.set('chartOptions', {
            responsive: true,
            maintainAspectRatio: false,
            layout: {
                padding: {
                    top: 5, // Needed otherwise the top dot is cut
                    right: 10,
                    bottom: 5,
                    left: 10
                }
            },
            title: {
                display: false
            },
            tooltips: {
                intersect: false,
                mode: 'index',
                displayColors: false,
                backgroundColor: '#343f44',
                xPadding: 7,
                yPadding: 7,
                cornerRadius: 5,
                caretSize: 7,
                caretPadding: 5,
                bodyFontSize: 13,
                titleFontStyle: 'normal',
                titleFontColor: 'rgba(255, 255, 255, 0.7)',
                titleMarginBottom: 4
            },
            hover: {
                mode: 'index',
                intersect: false,
                animationDuration: 120
            },
            legend: {
                display: false
            },
            scales: {
                xAxes: [{
                    labelString: 'Date',
                    gridLines: {
                        drawTicks: false,
                        color: (this.nightShift ? '#333F44' : '#E5EFF5'),
                        zeroLineColor: (this.nightShift ? '#333F44' : '#E5EFF5')
                    },
                    ticks: {
                        display: false,
                        maxRotation: 0,
                        minRotation: 0,
                        padding: 6,
                        autoSkip: false,
                        maxTicksLimit: 10,
                        callback: function (value, index, values) {
                            let step = (values.length - 1) / (maxTicksAllowed);
                            let steps = [];
                            for (let i = 0; i < maxTicksAllowed; i++) {
                                steps.push(Math.round(i * step));
                            }

                            if (index === 0) {
                                return value;
                            }
                            if (index === (values.length - 1)) {
                                return 'Today';
                            }

                            if (steps.includes(index)) {
                                return '';
                            }
                        }
                    }
                }],
                yAxes: [{
                    gridLines: {
                        drawTicks: false,
                        display: false,
                        drawBorder: false
                    },
                    ticks: {
                        maxTicksLimit: 5,
                        fontColor: '#9baeb8',
                        padding: 8,
                        precision: 0
                    }
                }]
            }
        });
    },

    getTicksForRange(rangeInDays) {
        if (rangeInDays <= 30) {
            return 6;
        } else if (rangeInDays <= 90) {
            return 18;
        } else {
            return 24;
        }
    },

    setChartJSDefaults() {
        Chart.defaults.LineWithLine = Chart.defaults.line;
        Chart.controllers.LineWithLine = Chart.controllers.line.extend({
            draw: function (ease) {
                Chart.controllers.line.prototype.draw.call(this, ease);

                if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
                    let activePoint = this.chart.tooltip._active[0];
                    let ctx = this.chart.ctx;
                    let x = activePoint.tooltipPosition().x;
                    let topY = this.chart.scales['y-axis-0'].top;
                    let bottomY = this.chart.scales['y-axis-0'].bottom;

                    // draw line
                    ctx.save();
                    ctx.beginPath();
                    ctx.moveTo(x, topY);
                    ctx.lineTo(x, bottomY);
                    ctx.lineWidth = 1;
                    ctx.strokeStyle = (this.nightShift ? 'rgba(62, 176, 239, 0.65)' : 'rgba(62, 176, 239, 0.8)');
                    ctx.stroke();
                    ctx.restore();
                }
            }
        });
    }
});
<commit_msg>Added 'thousands' separator to members chart<commit_after>/* global Chart */
import Component from '@ember/component';
import {action} from '@ember/object';
import {computed, get} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';

export default Component.extend({
    ajax: service(),
    membersStats: service(),

    // public attrs
    nightShift: false,

    stats: null,
    chartData: null,
    chartOptions: null,
    startDateLabel: '',

    selectedRange: computed('membersStats.days', function () {
        const availableRanges = this.availableRanges;
        return availableRanges.findBy('days', this.membersStats.days);
    }),

    availableRanges: computed(function () {
        return [{
            name: '30 days',
            days: '30'
        }, {
            name: '90 days',
            days: '90'
        }, {
            name: '365 days',
            days: '365'
        }, {
            name: 'All time',
            days: 'all-time'
        }];
    }),

    // Lifecycle ---------------------------------------------------------------

    init() {
        this._super(...arguments);
        this.setChartJSDefaults();
    },

    didReceiveAttrs() {
        if (this._lastNightShift !== undefined && this.nightShift !== this._lastNightShift) {
            this.setChartOptions();
        }
        this._lastNightShift = this.nightShift;
    },

    // Actions -----------------------------------------------------------------

    changeDateRange: action(function (range) {
        this.membersStats.days = get(range, 'days');
        this.fetchStatsTask.perform();
    }),

    // Tasks -------------------------------------------------------------------

    fetchStatsTask: task(function* () {
        this.set('stats', null);

        let stats = yield this.membersStats.fetch();

        if (stats) {
            this.set('stats', stats);

            this.setChartOptions({
                rangeInDays: Object.keys(stats.total_on_date).length
            });

            this.setChartData({
                dateLabels: Object.keys(stats.total_on_date),
                dateValues: Object.values(stats.total_on_date)
            });
        }
    }),

    // Internal ----------------------------------------------------------------

    setChartData({dateLabels, dateValues}) {
        this.set('chartData', {
            labels: dateLabels,
            datasets: [{
                label: 'Total members',
                cubicInterpolationMode: 'monotone',
                data: dateValues,
                fill: false,
                backgroundColor: 'rgba(62,176,239,.9)',
                pointRadius: 0,
                pointHitRadius: 10,
                borderColor: 'rgba(62,176,239,.9)',
                borderJoinStyle: 'round'
            }]
        });
    },

    setChartOptions({rangeInDays}) {
        let maxTicksAllowed = this.getTicksForRange(rangeInDays);

        this.setChartJSDefaults();

        this.set('chartOptions', {
            responsive: true,
            maintainAspectRatio: false,
            layout: {
                padding: {
                    top: 5, // Needed otherwise the top dot is cut
                    right: 10,
                    bottom: 5,
                    left: 10
                }
            },
            title: {
                display: false
            },
            tooltips: {
                intersect: false,
                mode: 'index',
                displayColors: false,
                backgroundColor: '#343f44',
                xPadding: 7,
                yPadding: 7,
                cornerRadius: 5,
                caretSize: 7,
                caretPadding: 5,
                bodyFontSize: 13,
                titleFontStyle: 'normal',
                titleFontColor: 'rgba(255, 255, 255, 0.7)',
                titleMarginBottom: 4,
                callbacks: {
                    label: function (tooltipItems, data) {
                        return data.datasets[0].label + `: ` + data.datasets[0].data[tooltipItems.index].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
                    }
                }
            },
            hover: {
                mode: 'index',
                intersect: false,
                animationDuration: 120
            },
            legend: {
                display: false
            },
            scales: {
                xAxes: [{
                    labelString: 'Date',
                    gridLines: {
                        drawTicks: false,
                        color: (this.nightShift ? '#333F44' : '#E5EFF5'),
                        zeroLineColor: (this.nightShift ? '#333F44' : '#E5EFF5')
                    },
                    ticks: {
                        display: false,
                        maxRotation: 0,
                        minRotation: 0,
                        padding: 6,
                        autoSkip: false,
                        maxTicksLimit: 10,
                        callback: function (value, index, values) {
                            let step = (values.length - 1) / (maxTicksAllowed);
                            let steps = [];
                            for (let i = 0; i < maxTicksAllowed; i++) {
                                steps.push(Math.round(i * step));
                            }

                            if (index === 0) {
                                return value;
                            }
                            if (index === (values.length - 1)) {
                                return 'Today';
                            }

                            if (steps.includes(index)) {
                                return '';
                            }
                        }
                    }
                }],
                yAxes: [{
                    gridLines: {
                        drawTicks: false,
                        display: false,
                        drawBorder: false
                    },
                    ticks: {
                        maxTicksLimit: 5,
                        fontColor: '#9baeb8',
                        padding: 8,
                        precision: 0,
                        callback: function (value) {
                            return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
                        }
                    }
                }]
            }
        });
    },

    getTicksForRange(rangeInDays) {
        if (rangeInDays <= 30) {
            return 6;
        } else if (rangeInDays <= 90) {
            return 18;
        } else {
            return 24;
        }
    },

    setChartJSDefaults() {
        Chart.defaults.LineWithLine = Chart.defaults.line;
        Chart.controllers.LineWithLine = Chart.controllers.line.extend({
            draw: function (ease) {
                Chart.controllers.line.prototype.draw.call(this, ease);

                if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
                    let activePoint = this.chart.tooltip._active[0];
                    let ctx = this.chart.ctx;
                    let x = activePoint.tooltipPosition().x;
                    let topY = this.chart.scales['y-axis-0'].top;
                    let bottomY = this.chart.scales['y-axis-0'].bottom;

                    // draw line
                    ctx.save();
                    ctx.beginPath();
                    ctx.moveTo(x, topY);
                    ctx.lineTo(x, bottomY);
                    ctx.lineWidth = 1;
                    ctx.strokeStyle = (this.nightShift ? 'rgba(62, 176, 239, 0.65)' : 'rgba(62, 176, 239, 0.8)');
                    ctx.stroke();
                    ctx.restore();
                }
            }
        });
    }
});
<|endoftext|>


<commit_before>import _ from 'lodash';
import { Definition } from 'classes/helpers';
import api from '../../plugins/http/api';
import { undoStackInstance } from 'plugins/undo-redo';

const state = {
	currentBlockIndex: null,
	currentRegion: 'main',
	blockMeta: {
		sizes: [],
		offsets: [],
		moved: null
	},
	pageData: {
		blocks: {
			main: []
		}
	},
	pageName: '',
	pageScale: .4,
	loaded: false
};

const mutations = {

	setPage(state, page) {
		state.loaded = true;

		if(!page.blocks) {
			page.blocks = {
				main: []
			};
		}

		state.pageData = page;
	},

	setBlock(state, { index } = { index: null }) {
		state.currentBlockIndex = index;
	},

	reorderBlocks(state, { from, to, value }) {
		state.pageData.blocks[state.currentRegion].splice(from, 1);
		state.pageData.blocks[state.currentRegion].splice(to, 0, value);
	},

	updateBlockPositionsOrder(state, { type, from, to, value }) {
		state.blockMeta[type].splice(from, 1);
		state.blockMeta[type].splice(to, 0, value);
	},

	updateBlockPositions(state, { type, index, value }) {
		state.blockMeta[type].splice(index, 1, value);
	},

	updateFieldValue(state, { index, name, value }) {
		let
			idx = index !== void 0 ? index : state.currentBlockIndex,
			fields = state.pageData.blocks[state.currentRegion][idx].fields;

		// if field exists just update it
		if(fields[name] !== void 0) {
			fields[name] = value;
		}
		// otherwise update all fields to maintain reactivity
		else {
			fields = {
				...fields,
				...{ [name]: value }
			};
		}
	},

	changePage(state, name) {
		state.pageName = name;
	},

	addBlock(state, { region, index, block }) {
		if(region === void 0) {
			region = state.currentRegion;
		}

		if(index === void 0) {
			index = state.pageData.blocks[state.currentRegion].length;
		}

		Definition.fillBlockFields(block);

		state.pageData.blocks[state.currentRegion].splice(index, 1, block || {});
	},

	deleteBlock(state, { index } = { index: null }) {
		if(index === null) {
			index = state.currentBlockIndex;
		}

		state.pageData.blocks[state.currentRegion].splice(index, 1);
	}
};

const actions = {

	fetchPage({ commit }, id) {

		// TODO: refactor into smaller methods
		api
			.get(`page/${id}?include=blocks`)
			.then(response => {
				const page = response.data.data;

				api
					.get(`layout/${page.layout_name}/definition?include=region_definitions.block_definitions`)
					.then(({ data: region }) => {

						region.data.region_definitions.forEach((region) => {
							region.block_definitions.forEach(definition => {
								Definition.set(definition);
							});
						});

						commit('setBlockDefinitions', Definition.definitions, { root: true });

						let blocks;

						if(page.blocks) {
							blocks = page.blocks;
							delete page.blocks;
						}
						else {
							blocks = {};
						}

						commit('setPage', _.cloneDeep(page));

						Object.keys(blocks).forEach(region => {
							blocks[region].forEach((block, index) => {
								commit('addBlock', { region, index, block })
							});
						});

						undoStackInstance.init(page);
					});

			});
	}
};

const getters = {

	getFieldValue: (state) => (index, name) => {
		return state.pageData.blocks[state.currentRegion][index].fields[name];
	},

	getCurrentFieldValue: (state, getters) => (name) => {
		return getters.getFieldValue(state.currentBlockIndex, name);
	},

	getCurrentBlock: (state) => () => {
		return state.pageData.blocks[state.currentRegion][state.currentBlockIndex];
	}
};

export default {
	state,
	mutations,
	actions,
	getters
};
<commit_msg>Add support for setting/getting nested data from a field.<commit_after>import _ from 'lodash';
import { Definition } from 'classes/helpers';
import api from 'plugins/http/api';
import { undoStackInstance } from 'plugins/undo-redo';

const state = {
	currentBlockIndex: null,
	currentRegion: 'main',
	blockMeta: {
		sizes: [],
		offsets: [],
		moved: null
	},
	pageData: {
		blocks: {
			main: []
		}
	},
	pageName: '',
	pageScale: .4,
	loaded: false
};

const mutations = {

	setPage(state, page) {
		state.loaded = true;

		if(!page.blocks) {
			page.blocks = {
				main: []
			};
		}

		state.pageData = page;
	},

	setBlock(state, { index } = { index: null }) {
		state.currentBlockIndex = index;
	},

	reorderBlocks(state, { from, to, value }) {
		state.pageData.blocks[state.currentRegion].splice(from, 1);
		state.pageData.blocks[state.currentRegion].splice(to, 0, value);
	},

	updateBlockPositionsOrder(state, { type, from, to, value }) {
		state.blockMeta[type].splice(from, 1);
		state.blockMeta[type].splice(to, 0, value);
	},

	updateBlockPositions(state, { type, index, value }) {
		state.blockMeta[type].splice(index, 1, value);
	},

	updateFieldValue(state, { index, name, value }) {
		let
			idx = index !== void 0 ? index : state.currentBlockIndex,
			fields = state.pageData.blocks[state.currentRegion][idx].fields;

		// if field exists just update it
		if(_.has(fields, name)) {
			_.set(fields, name, value);
		}
		// otherwise update all fields to maintain reactivity
		else {
			const clone = _.clone(fields);
			_.set(clone, name, value);
			fields = clone;
		}
	},

	changePage(state, name) {
		state.pageName = name;
	},

	addBlock(state, { region, index, block }) {
		if(region === void 0) {
			region = state.currentRegion;
		}

		if(index === void 0) {
			index = state.pageData.blocks[state.currentRegion].length;
		}

		Definition.fillBlockFields(block);

		state.pageData.blocks[state.currentRegion].splice(index, 1, block || {});
	},

	deleteBlock(state, { index } = { index: null }) {
		if(index === null) {
			index = state.currentBlockIndex;
		}

		state.pageData.blocks[state.currentRegion].splice(index, 1);
	}
};

const actions = {

	fetchPage({ commit }, id) {

		// TODO: refactor into smaller methods
		api
			.get(`page/${id}?include=blocks`)
			.then(response => {
				const page = response.data.data;

				api
					.get(`layout/${page.layout_name}/definition?include=region_definitions.block_definitions`)
					.then(({ data: region }) => {

						region.data.region_definitions.forEach((region) => {
							region.block_definitions.forEach(definition => {
								Definition.set(definition);
							});
						});

						commit('setBlockDefinitions', Definition.definitions, { root: true });

						let blocks;

						if(page.blocks) {
							blocks = page.blocks;
							delete page.blocks;
						}
						else {
							blocks = {};
						}

						commit('setPage', _.cloneDeep(page));

						Object.keys(blocks).forEach(region => {
							blocks[region].forEach((block, index) => {
								commit('addBlock', { region, index, block })
							});
						});
					});

			});
	}
};

const getters = {

	getFieldValue: (state) => (index, name) => {
		const block = state.pageData.blocks[state.currentRegion][index];

		if(!block) {
			return null;
		}

		return _.get(block.fields, name, null);
	},

	getCurrentFieldValue: (state, getters) => (name) => {
		return getters.getFieldValue(state.currentBlockIndex, name);
	},

	getCurrentBlock: (state) => () => {
		return state.pageData.blocks[state.currentRegion][state.currentBlockIndex];
	}
};

export default {
	state,
	mutations,
	actions,
	getters
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name           Github CommitDiff Viewer
// @author         Jun Hashimoto
// @description    Add diff link for github commit list page.
// @include        https://github.com/*
// @version        1.0.4
// @require        https://code.jquery.com/jquery-3.2.1.min.js
// ==/UserScript==

(function() {
  'use strict';

  class CommitDiffUIManager {
  }

  // execute UserScript on browser, and export NGWordManager class on test
  if (typeof window === 'object') {
  } else {
    module.exports = CommitDiffUIManager;
  }
})();

var buttonArea = $('.pagehead-actions');

var makeDiffButton = function() {
    var diffButton = $('
Show Diff
');
    diffButton.bind("click", showDiffLink);
    $('.pagehead-actions').append(diffButton);
};

var removeDiffButton = function() {
    if ($('li#showDiffButton')) {
        $('li#showDiffButton').remove();
    }
};

var makeRadioButtons = function() {
    var commitAreas = $('.commit-links-cell');
    commitAreas.each(function(){
        $(this).css('width', '350px');
        var commitLink = $(this).find('.zeroclipboard-button:first');
        var commitHash = commitLink.attr('data-clipboard-text');
        var fromRadioButton = makeRadioButton('commitFrom', 'from', commitHash);
        var toRadioButton   = makeRadioButton('commitTo', 'to', commitHash);
        $(this).append(fromRadioButton);
        $(this).append(toRadioButton);
    });
};

var makeRadioButton = function(propertyName, textName, commitHash){
    return $('<input type="radio" name=' + propertyName + ' value=' + commitHash + '></input>' + textName + '');
};

var showDiffLink = function() {
    var fromCommit = getCommitHash('commitFrom');
    var toCommit   = getCommitHash('commitTo');
    if (fromCommit && toCommit) {
        var currentUrl      = location.href;
        var commitUrlHeader = getCommitUrl(currentUrl);
        var commitUrl       = commitUrlHeader + fromCommit + '...' + toCommit;
        location.href       = commitUrl;
    }
};

var getCommitHash = function(className) {
    var radioButtonAttribute = 'input[name=' + className + ']:checked';
    return $(radioButtonAttribute).attr('value');
};

var getCommitUrl = function(currentUrl) {
    return currentUrl.replace(/commits\/(\S+)/, 'compare/');
};

// main function
var main = function() {
    if( location.pathname.match(/\/commits\//) ) {
        makeDiffButton();
        makeRadioButtons();
    } else {
        removeDiffButton();
    }
};

unsafeWindow.$(unsafeWindow.document).on('pjax:end', function(){
    main();
});

$(document).ready(function() {
    main();
});
<commit_msg>refactor with ES6<commit_after>// ==UserScript==
// @name           Github CommitDiff Viewer
// @author         Jun Hashimoto
// @description    Add diff link for github commit list page.
// @include        https://github.com/*
// @version        1.0.4
// @require        https://code.jquery.com/jquery-3.2.1.min.js
// ==/UserScript==

(function() {
  'use strict';

  class CommitDiffUIManager {
    // repositoryUrl: https://github.com/xxx/yyy
    constructor(repositoryUrl) {
      this.repositoryUrl = repositoryUrl;
    }

    static get UI_CONSTANTS() {
      return {
        selector: {
          commitArea: '.commit-links-cell',
          commitLink: 'div.BtnGroup .zeroclipboard-button:first',
          headerButtonArea: '.pagehead-actions',
          radioInput: 'input.commitHash',
          commitDiffButton: 'li#showDiffButton'
        },
        attribute: {
          commitHash: 'data-clipboard-text'
        },
        dom: {
          radioInputPrefix: '<input type="radio" name=',
          commitDiffButton: '
Show Diff
'
        }
      };
    }

    // Build UI
    buildUI(currentUrl) {
      if (currentUrl.match(/\/commits\//)) {
        if (!$(CommitDiffUIManager.UI_CONSTANTS['selector']['radioInput']).length) {
          this.createRadioInputs();
        }
        if (!$(CommitDiffUIManager.UI_CONSTANTS['selector']['commitDiffButton']).length) {
          this.createCommitDiffButton();
        }
      }
    }

    createRadioInputs() {
      let that = this;

      $(CommitDiffUIManager.UI_CONSTANTS['selector']['commitArea']).each(function() {
        let commitLink = $(this).find(CommitDiffUIManager.UI_CONSTANTS['selector']['commitLink']);
        let commitHash = commitLink.attr(CommitDiffUIManager.UI_CONSTANTS['attribute']['commitHash']);

        let fromRadioButton = that.createRadioInput('commitFrom', 'from', commitHash);
        let toRadioButton   = that.createRadioInput('commitTo', 'to', commitHash);

        commitLink.before(fromRadioButton);
        commitLink.before(toRadioButton);
    });

    }
    createRadioInput(propertyName, textName, commitHash) {
      return $(CommitDiffUIManager.UI_CONSTANTS['dom']['radioInputPrefix'] + propertyName + ' value=' + commitHash + '> ' + textName + '');
    }

    createCommitDiffButton() {
      let diffButton = $(CommitDiffUIManager.UI_CONSTANTS['dom']['commitDiffButton']);
      diffButton.bind('click', this.showCommitCompareUrl());

      $(CommitDiffUIManager.UI_CONSTANTS['selector']['headerButtonArea']).append(diffButton);
    }

    // Show commit compare URL
    showCommitCompareUrl() {
      const func = () => {
        let commitCompareUrl = this.createCommitCompareUrl();

        if (commitCompareUrl) {
          location.href = commitCompareUrl;
        }
      };

      return func;
    }

    createCommitCompareUrl() {
      let fromCommit = this.getCommitHash('commitFrom');
      let toCommit   = this.getCommitHash('commitTo');

      if (fromCommit && toCommit) {
        return this.repositoryUrl + '/compare/' + fromCommit + '...' + toCommit;
      } else {
        return undefined;
      }
    }

    getCommitHash(className) {
      let radioButtonSelector = 'input[name=' + className + ']:checked';
      return $(radioButtonSelector).attr('value');
    }
  }

  // execute UserScript on browser, and export NGWordManager class on test
  if (typeof window === 'object') {
    const repositoryUrlRegExp = /https:\/\/github.com\/[^/]+\/[^/]+/;

    let runUserScript = () => {
      let match = location.href.match(repositoryUrlRegExp);
      if (match) {
        let repositoryUrl = match[0];
        let commitDiffUIManager = new CommitDiffUIManager(repositoryUrl);

        commitDiffUIManager.buildUI(location.href);

        $(document).on('pjax:end', () => {
          commitDiffUIManager.buildUI(location.href);
        });
      }
    };

    runUserScript();
  } else {
    module.exports = CommitDiffUIManager;
  }
})();

/*
var buttonArea = $('.pagehead-actions');

var makeDiffButton = function() {
    var diffButton = $('
Show Diff
');
    diffButton.bind("click", showDiffLink);
    $('.pagehead-actions').append(diffButton);
};

var removeDiffButton = function() {
    if ($('li#showDiffButton')) {
        $('li#showDiffButton').remove();
    }
};

var makeRadioButtons = function() {
    var commitAreas = $('.commit-links-cell');
    commitAreas.each(function(){
        $(this).css('width', '350px');
        var commitLink = $(this).find('.zeroclipboard-button:first');
        var commitHash = commitLink.attr('data-clipboard-text');
        var fromRadioButton = makeRadioButton('commitFrom', 'from', commitHash);
        var toRadioButton   = makeRadioButton('commitTo', 'to', commitHash);
        $(this).append(fromRadioButton);
        $(this).append(toRadioButton);
    });
};

var makeRadioButton = function(propertyName, textName, commitHash){
    return $('<input type="radio" name=' + propertyName + ' value=' + commitHash + '></input>' + textName + '');
};

var showDiffLink = function() {
    var fromCommit = getCommitHash('commitFrom');
    var toCommit   = getCommitHash('commitTo');
    if (fromCommit && toCommit) {
        var currentUrl      = location.href;
        var commitUrlHeader = getCommitUrl(currentUrl);
        var commitUrl       = commitUrlHeader + fromCommit + '...' + toCommit;
        location.href       = commitUrl;
    }
};

var getCommitHash = function(className) {
    var radioButtonAttribute = 'input[name=' + className + ']:checked';
    return $(radioButtonAttribute).attr('value');
};

var getCommitUrl = function(currentUrl) {
    return currentUrl.replace(/commits\/(\S+)/, 'compare/');
};

// main function
var main = function() {
    if( location.pathname.match(/\/commits\//) ) {
        makeDiffButton();
        makeRadioButtons();
    } else {
        removeDiffButton();
    }
};

unsafeWindow.$(unsafeWindow.document).on('pjax:end', function(){
    main();
});

$(document).ready(function() {
    main();
});
*/
<|endoftext|>


<commit_before>import React from 'react';
import {connect} from 'react-redux';
import { Form, actions } from 'react-redux-form';
import isEmpty from 'lodash/isEmpty';
import isNumber from 'lodash/isNumber';

import {required, limitNumbers, validLinks, validABN} from '../../../../validators';

import Layout from '../../../../shared/Layout';

import BaseForm     from '../../../../shared/form/BaseForm';
import SubmitForm   from '../../../../shared/form/SubmitForm';
import ErrorBox     from '../../../../shared/form/ErrorBox';
import Textarea     from '../../../../shared/form/Textarea';
import Textfield    from '../../../../shared/form/Textfield';
import formProps    from '../../../../shared/reduxModules/formPropsSelector';
import StepNav      from '../StepNav';

import './BusinessDetailsForm.css'

class BusinessDetailsForm extends BaseForm {

    static propTypes = {
        action: React.PropTypes.string,
        csrf_token: React.PropTypes.string,
        form: React.PropTypes.object.isRequired,
        returnLink: React.PropTypes.string,
        supplierCode: React.PropTypes.number
    }

    onAdd(e) {
        e.preventDefault();
        const {model, addAddress, businessDetailsForm} = this.props;
        addAddress(model, businessDetailsForm.addresses || {});
    }

    onRemove(id, e) {
        e.preventDefault();
        const {model, removeAddress} = this.props;
        removeAddress(model, id);
    }

    render() {
        const {action, csrf_token, model, returnLink, supplierCode, form, buttonText, children, onSubmit, onSubmitFailed, businessDetailsForm, nextRoute } = this.props;
        let title = 'Tell us about your business'
        if (isNumber(supplierCode)) {
            title = 'Check your business details'
        }

        return (
            <Layout>

{title}

                    <ErrorBox focusOnMount={true} model={model}/>
                    <Form model={model}
                          action={action}
                          method="post"
                          id="BusinessDetails__create"
                          valid={form.valid}
                          component={SubmitForm}
                          onCustomSubmit={onSubmit}
                          onSubmitFailed={onSubmitFailed}
                    >
                        {csrf_token && (
                            <input type="hidden" name="csrf_token" id="csrf_token" value={csrf_token}/>
                        )}

                        <Textfield
                          model={`${model}.name`}
                          name="name"
                          id="name"
                          htmlFor="name"
                          label="Business name"
                          description="As you would like it shown on the Digital Marketplace."
                          validators={{required}}
                          messages={{
                              required: 'Business name is required',
                          }}
                        />

                        <Textfield
                          model={`${model}.abn`}
                          name="abn"
                          id="abn"
                          htmlFor="abn"
                          label="ABN"
                          description={isNumber(supplierCode) ? "You need an Australian Business Number to do business in Australia." :
                              (You need an Australian Business Number to do business in Australia.
                              Apply for an ABN here.
                          )}
                          readOnly={isNumber(supplierCode)}
                          disabled={isNumber(supplierCode)}
                          messages={{
                              validABN: 'ABN is required and must match a valid ABN as listed on the Australian Business Register'
                          }}
                          validators={{validABN}}
                        />

                        <Textarea
                            model={`${model}.summary`}
                            name="summary"
                            id="summary"
                            controlProps={{limit: 50}}
                            label="Summary"
                            description="3-4 sentences that describe your business. This can be seen by all Digital Marketplace visitors without signing in."
                            showMessagesDuringFocus={true}
                            messages={{
                                required: 'You must provide a seller summary'
                            }}
                            validators={{required}}
                        />

                        <Textfield
                            model={`${model}.website`}
                            name="website"
                            id="website"
                            htmlFor="website"
                            label="Website URL"
                            description="Provide a link to your website beginning with http"
                            messages={{
                                required: 'You must provide a website link beginning with http',
                                validLinks: 'Links provided must begin with http'
                            }}
                            validators={{required, validLinks}}
                        />

                        <Textfield
                            model={`${model}.linkedin`}
                            name="linkedin"
                            id="linkedin"
                            htmlFor="linkedin"
                            label="LinkedIn URL (optional)"
                            description="Provide a LinkedIn URL beginning with http"
                            messages={{
                                validLinks: 'Links provided must begin with http'
                            }}
                            validators={{validLinks}}
                        />

                        <Textfield
                            model={`${model}.addresses.0.address_line`}
                            name="address.address_line"
                            id="address_line"
                            htmlFor="address_line"
                            label="Primary Address"
                            description="Principal place of business."
                            messages={{
                                required: 'You must provide an address'
                            }}
                            validators={{required}}
                        />

                        <Textfield
                            model={`${model}.addresses.0.suburb`}
                            name="address.suburb"
                            id="suburb"
                            htmlFor="suburb"
                            label="Suburb"
                            messages={{
                                required: 'You must provide a suburb'
                            }}
                            validators={{required}}
                        />
                        <Textfield
                            model={`${model}.addresses.0.state`}
                            name="address.state"
                            id="state"
                            htmlFor="state"
                            label="State"
                            messages={{
                                required: 'You must provide a state'
                            }}
                            validators={{required}}
                        />
                        <Textfield
                            model={`${model}.addresses.0.postal_code`}
                            name="address.postal_code"
                            id="postal_code"
                            htmlFor="postal_code"
                            label="Postcode"
                            maxLength="4"
                            messages={{
                                required: 'You must provide a postal code. ',
                                limitNumbers: 'Postal codes must be four digits long and only numbers.'
                            }}
                            validators={{required, limitNumbers: limitNumbers(4)}}
                        />

                            {businessDetailsForm.addresses &&
                                Object.keys(businessDetailsForm.addresses)
                                    .filter((value) => {return value > 0 && businessDetailsForm.addresses[value] !== null;})
                                    .map((key, i) => {
                              return (

Additional address

                                            <button type="submit" styleName="remove-button" className="button-secondary" onClick={this.onRemove.bind(this, key)}>Remove</button>

                                            <Textfield
                                                model={`${model}.addresses.${key}.address_line`}
                                                name={`address_line-${key}`}
                                                id={`address_line-${key}`}
                                                htmlFor={`address_line-${key}`}
                                                label="Address"
                                                messages={{
                                                    required: 'You must provide address'
                                                }}
                                                validators={{required}}
                                            />
                                            <Textfield
                                                model={`${model}.addresses.${key}.suburb`}
                                                name={`suburb-${key}`}
                                                id={`suburb-${key}`}
                                                htmlFor={`suburb-${key}`}
                                                label="Suburb"
                                                messages={{
                                                    required: 'You must provide a suburb'
                                                }}
                                                validators={{required}}
                                            />
                                            <Textfield
                                                model={`${model}.addresses.${key}.state`}
                                                name={`state-${key}`}
                                                id={`state-${key}`}
                                                htmlFor={`state-${key}`}
                                                label="State"
                                                messages={{
                                                    required: 'You must provide a state'
                                                }}
                                                validators={{required}}
                                            />
                                            <Textfield
                                                model={`${model}.addresses.${key}.postal_code`}
                                                name={`postal_code-${key}`}
                                                id={`postal_code-${key}`}
                                                htmlFor={`postal_code-${key}`}
                                                label="Postcode"
                                                maxLength="4"
                                                messages={{
                                                    required: 'You must provide a postal code. ',
                                                    limitNumbers: 'Postal codes must be four digits long and only numbers.'
                                                }}
                                                validators={{required, limitNumbers: limitNumbers(4)}}
                                            />

                              )
                            })}
                            {(isEmpty(businessDetailsForm.addresses) || Object.keys(businessDetailsForm.addresses).length <= 1) &&

More offices?

                            }
                            <button type="submit" className="button-secondary" onClick={this.onAdd.bind(this)}>Add another address</button>

                        {children}

                        <StepNav buttonText={buttonText} to={nextRoute}/>
                    </Form>
                    {returnLink && Return without saving}

            </Layout>
        )
    }
}

BusinessDetailsForm.defaultProps = {
  buttonText: 'Update profile',
  title: 'Tell us about your business'
}

const mapStateToProps = (state) => {
    return {
        supplierCode: (state.application && state.application.supplier_code),
        returnLink: state.businessDetailsForm && state.businessDetailsForm.returnLink,
        ...formProps(state, 'businessDetailsForm')
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        addAddress: (model, addresses) => {
            dispatch(actions.change(`${model}.addresses.${Object.keys(addresses).length || 1}`, {}));
        },
        removeAddress: (model, id) => {
            dispatch(actions.omit(`${model}.addresses`, id));
            // added due to bug in adding empty address then removing without submit
            dispatch(actions.setValidity(`${model}.addresses.${id}`, true));
        }
    }
}

export {
    Textfield,
    mapStateToProps,
    BusinessDetailsForm as Form
}

export default connect(mapStateToProps, mapDispatchToProps)(BusinessDetailsForm);
<commit_msg>Update BusinessDetailsForm.js<commit_after>import React from 'react';
import {connect} from 'react-redux';
import { Form, actions } from 'react-redux-form';
import isEmpty from 'lodash/isEmpty';
import isNumber from 'lodash/isNumber';

import {required, limitNumbers, validLinks, validABN} from '../../../../validators';

import Layout from '../../../../shared/Layout';

import BaseForm     from '../../../../shared/form/BaseForm';
import SubmitForm   from '../../../../shared/form/SubmitForm';
import ErrorBox     from '../../../../shared/form/ErrorBox';
import Textarea     from '../../../../shared/form/Textarea';
import Textfield    from '../../../../shared/form/Textfield';
import formProps    from '../../../../shared/reduxModules/formPropsSelector';
import StepNav      from '../StepNav';

import './BusinessDetailsForm.css'

class BusinessDetailsForm extends BaseForm {

    static propTypes = {
        action: React.PropTypes.string,
        csrf_token: React.PropTypes.string,
        form: React.PropTypes.object.isRequired,
        returnLink: React.PropTypes.string,
        supplierCode: React.PropTypes.number
    }

    onAdd(e) {
        e.preventDefault();
        const {model, addAddress, businessDetailsForm} = this.props;
        addAddress(model, businessDetailsForm.addresses || {});
    }

    onRemove(id, e) {
        e.preventDefault();
        const {model, removeAddress} = this.props;
        removeAddress(model, id);
    }

    render() {
        const {action, csrf_token, model, returnLink, supplierCode, form, buttonText, children, onSubmit, onSubmitFailed, businessDetailsForm, nextRoute } = this.props;
        let title = 'Tell us about your business'
        if (isNumber(supplierCode)) {
            title = 'Check your business details'
        }

        return (
            <Layout>

{title}

                    <ErrorBox focusOnMount={true} model={model}/>
                    <Form model={model}
                          action={action}
                          method="post"
                          id="BusinessDetails__create"
                          valid={form.valid}
                          component={SubmitForm}
                          onCustomSubmit={onSubmit}
                          onSubmitFailed={onSubmitFailed}
                    >
                        {csrf_token && (
                            <input type="hidden" name="csrf_token" id="csrf_token" value={csrf_token}/>
                        )}

                        <Textfield
                          model={`${model}.name`}
                          name="name"
                          id="name"
                          htmlFor="name"
                          label="Business name"
                          description="As you would like it shown on the Digital Marketplace."
                          validators={{required}}
                          messages={{
                              required: 'Business name is required',
                          }}
                        />

                        <Textfield
                          model={`${model}.abn`}
                          name="abn"
                          id="abn"
                          htmlFor="abn"
                          label="ABN"
                          description={isNumber(supplierCode) ? "You need an Australian Business Number to do business in Australia." :
                              (You need an Australian Business Number to do business in Australia.
                              Apply for an ABN here.
                          )}
                          readOnly={isNumber(supplierCode)}
                          disabled={isNumber(supplierCode)}
                          messages={{
                              validABN: 'ABN is required and must match a valid ABN as listed on the Australian Business Register'
                          }}
                          validators={{validABN}}
                        />

                        <Textarea
                            model={`${model}.summary`}
                            name="summary"
                            id="summary"
                            controlProps={{limit: 50}}
                            label="Summary"
                            description="3-4 sentences that describe your business. This can be seen by all Digital Marketplace visitors without signing in."
                            showMessagesDuringFocus={true}
                            messages={{
                                required: 'You must provide a seller summary'
                            }}
                            validators={{required}}
                        />

                        <Textfield
                            model={`${model}.website`}
                            name="website"
                            id="website"
                            htmlFor="website"
                            label="Website URL"
                            description="Provide a link to your website beginning with http"
                            messages={{
                                required: 'You must provide a website link beginning with http',
                                validLinks: 'Links provided must begin with http'
                            }}
                            validators={{required, validLinks}}
                        />

                        <Textfield
                            model={`${model}.linkedin`}
                            name="linkedin"
                            id="linkedin"
                            htmlFor="linkedin"
                            label="LinkedIn URL (optional)"
                            description="Provide a LinkedIn URL beginning with http"
                            messages={{
                                validLinks: 'Links provided must begin with http'
                            }}
                            validators={{validLinks}}
                        />

                        <Textfield
                            model={`${model}.addresses.0.address_line`}
                            name="address.address_line"
                            id="address_line"
                            htmlFor="address_line"
                            label="Primary Address"
                            description="Principal place of business."
                            messages={{
                                required: 'You must provide an address'
                            }}
                            validators={{required}}
                        />

                        <Textfield
                            model={`${model}.addresses.0.suburb`}
                            name="address.suburb"
                            id="suburb"
                            htmlFor="suburb"
                            label="Suburb"
                            messages={{
                                required: 'You must provide a suburb'
                            }}
                            validators={{required}}
                        />
                        <Textfield
                            model={`${model}.addresses.0.state`}
                            name="address.state"
                            id="state"
                            htmlFor="state"
                            label="State"
                            messages={{
                                required: 'You must provide a state'
                            }}
                            validators={{required}}
                        />
                        <Textfield
                            model={`${model}.addresses.0.postal_code`}
                            name="address.postal_code"
                            id="postal_code"
                            htmlFor="postal_code"
                            label="Postcode"
                            maxLength="4"
                            messages={{
                                required: 'You must provide a postal code. ',
                                limitNumbers: 'Postal codes must be four digits long and only numbers.'
                            }}
                            validators={{required, limitNumbers: limitNumbers(4)}}
                        />

                            {businessDetailsForm.addresses &&
                                Object.keys(businessDetailsForm.addresses)
                                    .filter((value) => {return value > 0 && businessDetailsForm.addresses[value] !== null;})
                                    .map((key, i) => {
                              return (

Additional address

                                            <button type="submit" styleName="remove-button" className="button-secondary" onClick={this.onRemove.bind(this, key)}>Remove</button>

                                            <Textfield
                                                model={`${model}.addresses.${key}.address_line`}
                                                name={`address_line-${key}`}
                                                id={`address_line-${key}`}
                                                htmlFor={`address_line-${key}`}
                                                label="Address"
                                                messages={{
                                                    required: 'You must provide address'
                                                }}
                                                validators={{required}}
                                            />
                                            <Textfield
                                                model={`${model}.addresses.${key}.suburb`}
                                                name={`suburb-${key}`}
                                                id={`suburb-${key}`}
                                                htmlFor={`suburb-${key}`}
                                                label="Suburb"
                                                messages={{
                                                    required: 'You must provide a suburb'
                                                }}
                                                validators={{required}}
                                            />
                                            <Textfield
                                                model={`${model}.addresses.${key}.state`}
                                                name={`state-${key}`}
                                                id={`state-${key}`}
                                                htmlFor={`state-${key}`}
                                                label="State"
                                                messages={{
                                                    required: 'You must provide a state'
                                                }}
                                                validators={{required}}
                                            />
                                            <Textfield
                                                model={`${model}.addresses.${key}.postal_code`}
                                                name={`postal_code-${key}`}
                                                id={`postal_code-${key}`}
                                                htmlFor={`postal_code-${key}`}
                                                label="Postcode"
                                                maxLength="4"
                                                messages={{
                                                    required: 'You must provide a postal code. ',
                                                    limitNumbers: 'Postal codes must be four digits long and only numbers.'
                                                }}
                                                validators={{required, limitNumbers: limitNumbers(4)}}
                                            />

                              )
                            })}
                            {(isEmpty(businessDetailsForm.addresses) || Object.keys(businessDetailsForm.addresses).length <= 1) &&

More offices?

                            }
                            <button type="submit" className="button-secondary" onClick={this.onAdd.bind(this)}>Add another address</button>

                        {children}

                        <StepNav buttonText={buttonText} to={nextRoute}/>
                    </Form>
                    {returnLink && Return without saving}

            </Layout>
        )
    }
}

BusinessDetailsForm.defaultProps = {
  buttonText: 'Update profile',
  title: 'Tell us about your business'
}

const mapStateToProps = (state) => {
    return {
        supplierCode: (state.application && state.application.supplier_code),
        returnLink: state.businessDetailsForm && state.businessDetailsForm.returnLink,
        ...formProps(state, 'businessDetailsForm')
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        addAddress: (model, addresses) => {
            dispatch(actions.change(`${model}.addresses.${Object.keys(addresses).length || 1}`, {}));
        },
        removeAddress: (model, id) => {
            dispatch(actions.omit(`${model}.addresses`, id));
            // added due to bug in adding empty address then removing without submit
            dispatch(actions.setValidity(`${model}.addresses.${id}`, true));
        }
    }
}

export {
    Textfield,
    mapStateToProps,
    BusinessDetailsForm as Form
}

export default connect(mapStateToProps, mapDispatchToProps)(BusinessDetailsForm);
<|endoftext|>