<commit_before>// TODO: Combine this Country model with the one in projects.

App.Country = DS.Model.extend({
    url: "geo/countries",
    name: DS.attr('string'),
    code: DS.attr('string'),
    oda: DS.attr('boolean')
});

App.CountrySelectView = Em.Select.extend({
    content:  [{"id": "0", "name": "--loading--"}],
    optionValuePath: "content.id",
    optionLabelPath: "content.name",
    prompt: gettext("Pick a country")
});

App.CountryCodeSelectView = Em.Select.extend({
    content:  [{"code": "0", "name": "--loading--"}],
    optionValuePath: "content.code",
    optionLabelPath: "content.name",
    prompt: gettext("Pick a country")
});

App.ProjectCountrySelectView = Em.Select.extend({
    content:  [{"id": "0", "name": "--loading--"}],
    optionValuePath: "content.id",
    optionLabelPath: "content.name",
    prompt: gettext("Pick a country")
});

App.TimeNeededList = [
    {value: 0.25, title: gettext("15 minutes")},
    {value: 0.5, title: gettext("half an hour")},
    {value: 1, title: gettext("up to one hour")},
    {value: 2, title: gettext("two hours")},
    {value: 4, title: gettext("half a day")},
    {value: 8, title: gettext("one day")},
    {value: 16, title: gettext("two days")},
    {value: 40, title: gettext("one week")},
    {value: 80, title: gettext("two weeks")},
    {value: 160, title: gettext("one month")}

];

App.TimeNeededSelectView = Em.Select.extend({
    content: App.TimeNeededList,
    optionValuePath: "content.value",
    optionLabelPath: "content.title"
});

App.Theme = DS.Model.extend({
    url:'utils/themes',
    title: DS.attr('string')
});

App.ThemeList = [
    {id: "0", title: gettext("--loading--")}
];

App.ThemeSelectView = Em.Select.extend({
    content: App.ThemeList,
    optionValuePath: "content.id",
    optionLabelPath: "content.title",
    prompt: "Pick a theme"
});

App.IsAuthorMixin = Em.Mixin.create({
    isAuthor: function () {
        var username = this.get('controllers.currentUser.username');
        var authorname = this.get('author.username');
        if (username) {
            return (username == authorname);
        }
        return false;
    }.property('author.username', 'controllers.currentUser.username')
});

App.DeleteModelMixin = Em.Mixin.create({
    deleteRecordOnServer: function () {
        var record = this.get('model');
        var transaction = this.get('store').transaction();
        transaction.add(record);
        record.deleteRecord();
        transaction.commit();
    }
});

/*
 Mixin that controllers with editable models can use. E.g. App.UserProfileController

 @see App.UserProfileRoute and App.UserProfileController to see it in action.
 */
App.Editable = Ember.Mixin.create({
    saved: false,

    startEditing: function() {
        var record = this.get('model');
        if (record.get('transaction.isDefault') == true) {
            this.transaction = this.get('store').transaction();
            this.transaction.add(record);
        }
    },

    updateRecordOnServer: function(){
        var controller = this;
        var model = this.get('model');
        model.one('becameInvalid', function(record) {
            controller.set('saving', false);
            model.set('errors', record.get('errors'));
        });

        model.one('didUpdate', function(){
            if (controller.get('nextStep')) {
                controller.transitionToRoute(controller.get('nextStep'));
            } else {
                controller.startEditing();
            }
        });

        model.one('didCreate', function(){
            if (controller.get('nextStep')) {
                controller.transitionToRoute(controller.get('nextStep'));
            } else {
                controller.startEditing();
            }
        });

        model.transaction.commit();
    },

    stopEditing: function() {
        var self = this;
        var record = this.get('model');
        var transaction = record.get('transaction');

        if (record.get('isDirty')) {
            Bootstrap.ModalPane.popup({
                classNames: ['modal'],
                heading: gettext('Save changed data?'),
                message: gettext('You have some unsaved changes. Do you want to save before you leave?'),
                primary: gettext('Save'),
                secondary: gettext('Cancel'),
                callback: function(opts, e) {
                    e.preventDefault();

                    if (opts.primary) {
                        self.save(record);
                    }

                    if (opts.secondary) {
                        transaction.rollback();
                    }
                }
            });
        }
    },

    save: function(record) {
        var controller = this;

        if (record.get('isDirty')) {
            this.set('saving', true);
            this.set('saved', false);
        }

        record.one('didUpdate', function() {
            // record was saved
            controller.set('saving', false);
            controller.set('saved', true);
        });

        record.get('transaction').commit();
    },

    saveButtonText: (function() {
        if (this.get('saving')) {
            return gettext('Saving');
        }

        return gettext('Save');
    }).property('saving')
});

App.UploadFile = Ember.TextField.extend({
    attributeBindings: ['name', 'accept'],
    type: 'file',
    change: function (evt) {
        var files = evt.target.files;
        var reader = new FileReader();
        var file = files[0];
        var view = this;

        reader.onload = function(e) {
            var preview = "";
            view.$().parents('form').find('.preview').remove();
            view.$().parent().after('
' + preview + '
');
        };
        reader.readAsDataURL(file);
        // Don't set this value. It will cause an error in some browsers.
        //this.set('value', file);
        this.set('controller.image', file);
    }
});

App.UploadFileView = Ember.TextField.extend({
    type: 'file',
    attributeBindings: ['name', 'accept'],

    contentBinding: 'parentView.controller.content',

    change: function(e) {
        var controller = this.get('controller');
        var files = e.target.files;
        for (var i = 0; i < files.length; i++) {
            var reader = new FileReader();
            var file = files[i];
            reader.readAsDataURL(file);

            // Replace src of the preview..
            var view = this;
            view.$().parents('form').find('.preview').attr('src', '/static/assets/images/loading.gif');
            reader.onload = function(e) {
                view.$().parents('form').find('.preview').attr('src',  e.target.result);
            }

            this.get('controller').addFile(file);
        }
        // Clear the input field after uploading.
        e.target.value = null;
    }
});

// See/Use App.DatePicker
App.DatePickerValue = Ember.TextField.extend({
    type: 'hidden',
    valueBinding: "parentView.value"
});

// See/Use App.DatePicker
App.DatePickerWidget = Ember.TextField.extend({

    dateBinding: "parentView.value",
    configBinding: "parentView.config",

    didInsertElement: function(){
        var config = this.get('config');
        this.$().datepicker(config);
        this.$().datepicker('setDate', this.get('date'));
    },

    change: function(){
        this.set('date', this.$().datepicker('getDate'));
    }
});

// This renders a TextField with the localized date.
// On click it will use jQuery UI date picker dialog so the user can select a date.
// valueBinding should bind to a  DS.attr('date') property of an Ember model.
App.DatePicker = Ember.ContainerView.extend({
    config: {changeMonth: true, changeYear: true, yearRange: "c-100:c+10"},
    childViews: [App.DatePickerValue, App.DatePickerWidget]
});

// The id will be the tag string.
App.Tag = DS.Model.extend({
    url: 'utils/tags',
    // Hack to make sure that newly added tags won't conflict when they are saved embedded.
    loadedData: function() {
        if (this.get('isDirty') === false) {
            this._super.apply(this, arguments);
        }
    }
});

App.TagField = Em.TextField.extend({
    keyUp: function(e){
        if (e.keyCode == 188) {
            e.preventDefault();
            var val = this.get('value');
            val = val.replace(',','');
            this.set('parentView.new_tag', val);
            this.get('parentView').addTag();
        }
    }
});

App.TagWidget = Em.View.extend({
    templateName: 'tag_widget',
    addTag: function(){
        if (this.get('new_tag')) {
            var new_tag = this.get('new_tag').toLowerCase();
            var tags = this.get('tags');
            // Try to create a new tag, it will fail if it's already in the local store, so catch that.
            try {
                var tag = App.Tag.createRecord({'id': new_tag});
            } catch(err) {
                var tag = App.Tag.find(new_tag);
            }
            tags.pushObject(tag);
            this.set('new_tag', '');
        }
    },
    removeTag: function(tag) {
        var tags = this.get('tags');
        tags.removeObject(tag);
    },
    didInsertElement: function(){
        this.$('.tag').typeahead({
            source: function (query, process) {
                return $.get('/api/utils/tags/' + query, function (data) {
                    return process(data);
                });
            }
        })
    }
});

/*
  Mixin for (Array)Controllers to limit the items viewed.

  'model' should hold the complete list.
  'items' holds de items to show and is the property to use in handlebars template.
  'canLoadMore' is a boolean to determine if there are still more items to show.
  'showMore' is the event to trigger to add more items.
  'perPage' is the initial and incremental number of items to show.
  'page' tracks the current page.
 */
App.ShowMoreItemsMixin = Em.Mixin.create({
    perPage: 5,
    page: 0,
    items: Em.A(),

    remainingItemCount: function(){
        if (this.get('model.length')) {
            return this.get('model.length') - (this.get('page')  * this.get('perPage'));

        }
        return '...';
    }.property('page', 'model.isLoaded', 'perPage'),

    canLoadMore: function(){
        var totalPages = Math.ceil(this.get('model.length') / this.get('perPage'));
        return totalPages > this.get('page');
    }.property('perPage', 'page'),

    loadInitial: function(){
        // If no items have been load yet, please do.
        if (this.get('items').length == 0) {
            this.showMore();
        }
    }.observes('model.isLoaded'),

    showMore: function() {
        var start = this.get('page') * this.get('perPage');
        this.incrementProperty('page');
        var end = this.get('page') * this.get('perPage');
        this.get('items').pushObjects(this.get('model').slice(start, end));
    }

});

App.PopOverMixin = Em.Mixin.create({
   didInsertElement: function(){
        this.$('.has-popover').popover({trigger: 'hover', placement: 'left'});
        this.$('.has-tooltip').tooltip({trigger: 'hover', placement: 'right'});
   }
});

App.MapPicker = Em.View.extend({

    templateName: 'map_picker',
    marker: null,

    submit: function(e){
        e.preventDefault();
        this.lookUpLocation();
    },

    lookUpLocation: function() {
        var address = this.get('lookup');
        var view = this;
        view.geocoder.geocode( {'address': address}, function(results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                view.placeMarker(results[0].geometry.location);
                view.set('latitude',  '' + results[0].geometry.location.lat().toString());
                view.set('longitude', '' + results[0].geometry.location.lng().toString());

            } else {
                alert('Geocode was not successful for the following reason: ' + status);
            }
        });
    },
    placeMarker: function (position) {
        var view = this;
        if (view.marker) {
            view.marker.setMap(null)
        }

        view.marker = new google.maps.Marker({
            position: position,
            map: view.map
        });
        view.map.panTo(position);
    },

    didInsertElement: function(){
        var view = this;
        this.geocoder = new google.maps.Geocoder();
        var view = this;
        var point = new google.maps.LatLng(view.get('latitude'), view.get('longitude'));
        var mapOptions = {
            zoom: 6,
            center: point,
            mapTypeId: google.maps.MapTypeId.ROADMAP
          };
        view.map = new google.maps.Map(this.$('.map-picker').get(0), mapOptions);

        view.placeMarker(point);

        google.maps.event.addListener(view.map, 'click', function(e) {
            var loc = {};
            view.set('latitude', e.latLng.lat().toString());
            view.set('longitude', e.latLng.lng().toString());
            view.placeMarker(e.latLng);
        });
    }

});

<commit_msg>`App.Theme` Ember model removed.<commit_after>// TODO: Combine this Country model with the one in projects.

App.Country = DS.Model.extend({
    url: "geo/countries",
    name: DS.attr('string'),
    code: DS.attr('string'),
    oda: DS.attr('boolean')
});

App.CountrySelectView = Em.Select.extend({
    content:  [{"id": "0", "name": "--loading--"}],
    optionValuePath: "content.id",
    optionLabelPath: "content.name",
    prompt: gettext("Pick a country")
});

App.CountryCodeSelectView = Em.Select.extend({
    content:  [{"code": "0", "name": "--loading--"}],
    optionValuePath: "content.code",
    optionLabelPath: "content.name",
    prompt: gettext("Pick a country")
});

App.ProjectCountrySelectView = Em.Select.extend({
    content:  [{"id": "0", "name": "--loading--"}],
    optionValuePath: "content.id",
    optionLabelPath: "content.name",
    prompt: gettext("Pick a country")
});

App.TimeNeededList = [
    {value: 0.25, title: gettext("15 minutes")},
    {value: 0.5, title: gettext("half an hour")},
    {value: 1, title: gettext("up to one hour")},
    {value: 2, title: gettext("two hours")},
    {value: 4, title: gettext("half a day")},
    {value: 8, title: gettext("one day")},
    {value: 16, title: gettext("two days")},
    {value: 40, title: gettext("one week")},
    {value: 80, title: gettext("two weeks")},
    {value: 160, title: gettext("one month")}

];

App.TimeNeededSelectView = Em.Select.extend({
    content: App.TimeNeededList,
    optionValuePath: "content.value",
    optionLabelPath: "content.title"
});

App.IsAuthorMixin = Em.Mixin.create({
    isAuthor: function () {
        var username = this.get('controllers.currentUser.username');
        var authorname = this.get('author.username');
        if (username) {
            return (username == authorname);
        }
        return false;
    }.property('author.username', 'controllers.currentUser.username')
});

App.DeleteModelMixin = Em.Mixin.create({
    deleteRecordOnServer: function () {
        var record = this.get('model');
        var transaction = this.get('store').transaction();
        transaction.add(record);
        record.deleteRecord();
        transaction.commit();
    }
});

/*
 Mixin that controllers with editable models can use. E.g. App.UserProfileController

 @see App.UserProfileRoute and App.UserProfileController to see it in action.
 */
App.Editable = Ember.Mixin.create({
    saved: false,

    startEditing: function() {
        var record = this.get('model');
        if (record.get('transaction.isDefault') == true) {
            this.transaction = this.get('store').transaction();
            this.transaction.add(record);
        }
    },

    updateRecordOnServer: function(){
        var controller = this;
        var model = this.get('model');
        model.one('becameInvalid', function(record) {
            controller.set('saving', false);
            model.set('errors', record.get('errors'));
        });

        model.one('didUpdate', function(){
            if (controller.get('nextStep')) {
                controller.transitionToRoute(controller.get('nextStep'));
            } else {
                controller.startEditing();
            }
        });

        model.one('didCreate', function(){
            if (controller.get('nextStep')) {
                controller.transitionToRoute(controller.get('nextStep'));
            } else {
                controller.startEditing();
            }
        });

        model.transaction.commit();
    },

    stopEditing: function() {
        var self = this;
        var record = this.get('model');
        var transaction = record.get('transaction');

        if (record.get('isDirty')) {
            Bootstrap.ModalPane.popup({
                classNames: ['modal'],
                heading: gettext('Save changed data?'),
                message: gettext('You have some unsaved changes. Do you want to save before you leave?'),
                primary: gettext('Save'),
                secondary: gettext('Cancel'),
                callback: function(opts, e) {
                    e.preventDefault();

                    if (opts.primary) {
                        self.save(record);
                    }

                    if (opts.secondary) {
                        transaction.rollback();
                    }
                }
            });
        }
    },

    save: function(record) {
        var controller = this;

        if (record.get('isDirty')) {
            this.set('saving', true);
            this.set('saved', false);
        }

        record.one('didUpdate', function() {
            // record was saved
            controller.set('saving', false);
            controller.set('saved', true);
        });

        record.get('transaction').commit();
    },

    saveButtonText: (function() {
        if (this.get('saving')) {
            return gettext('Saving');
        }

        return gettext('Save');
    }).property('saving')
});

App.UploadFile = Ember.TextField.extend({
    attributeBindings: ['name', 'accept'],
    type: 'file',
    change: function (evt) {
        var files = evt.target.files;
        var reader = new FileReader();
        var file = files[0];
        var view = this;

        reader.onload = function(e) {
            var preview = "";
            view.$().parents('form').find('.preview').remove();
            view.$().parent().after('
' + preview + '
');
        };
        reader.readAsDataURL(file);
        // Don't set this value. It will cause an error in some browsers.
        //this.set('value', file);
        this.set('controller.image', file);
    }
});

App.UploadFileView = Ember.TextField.extend({
    type: 'file',
    attributeBindings: ['name', 'accept'],

    contentBinding: 'parentView.controller.content',

    change: function(e) {
        var controller = this.get('controller');
        var files = e.target.files;
        for (var i = 0; i < files.length; i++) {
            var reader = new FileReader();
            var file = files[i];
            reader.readAsDataURL(file);

            // Replace src of the preview..
            var view = this;
            view.$().parents('form').find('.preview').attr('src', '/static/assets/images/loading.gif');
            reader.onload = function(e) {
                view.$().parents('form').find('.preview').attr('src',  e.target.result);
            }

            this.get('controller').addFile(file);
        }
        // Clear the input field after uploading.
        e.target.value = null;
    }
});

// See/Use App.DatePicker
App.DatePickerValue = Ember.TextField.extend({
    type: 'hidden',
    valueBinding: "parentView.value"
});

// See/Use App.DatePicker
App.DatePickerWidget = Ember.TextField.extend({

    dateBinding: "parentView.value",
    configBinding: "parentView.config",

    didInsertElement: function(){
        var config = this.get('config');
        this.$().datepicker(config);
        this.$().datepicker('setDate', this.get('date'));
    },

    change: function(){
        this.set('date', this.$().datepicker('getDate'));
    }
});

// This renders a TextField with the localized date.
// On click it will use jQuery UI date picker dialog so the user can select a date.
// valueBinding should bind to a  DS.attr('date') property of an Ember model.
App.DatePicker = Ember.ContainerView.extend({
    config: {changeMonth: true, changeYear: true, yearRange: "c-100:c+10"},
    childViews: [App.DatePickerValue, App.DatePickerWidget]
});

// The id will be the tag string.
App.Tag = DS.Model.extend({
    url: 'utils/tags',
    // Hack to make sure that newly added tags won't conflict when they are saved embedded.
    loadedData: function() {
        if (this.get('isDirty') === false) {
            this._super.apply(this, arguments);
        }
    }
});

App.TagField = Em.TextField.extend({
    keyUp: function(e){
        if (e.keyCode == 188) {
            e.preventDefault();
            var val = this.get('value');
            val = val.replace(',','');
            this.set('parentView.new_tag', val);
            this.get('parentView').addTag();
        }
    }
});

App.TagWidget = Em.View.extend({
    templateName: 'tag_widget',
    addTag: function(){
        if (this.get('new_tag')) {
            var new_tag = this.get('new_tag').toLowerCase();
            var tags = this.get('tags');
            // Try to create a new tag, it will fail if it's already in the local store, so catch that.
            try {
                var tag = App.Tag.createRecord({'id': new_tag});
            } catch(err) {
                var tag = App.Tag.find(new_tag);
            }
            tags.pushObject(tag);
            this.set('new_tag', '');
        }
    },
    removeTag: function(tag) {
        var tags = this.get('tags');
        tags.removeObject(tag);
    },
    didInsertElement: function(){
        this.$('.tag').typeahead({
            source: function (query, process) {
                return $.get('/api/utils/tags/' + query, function (data) {
                    return process(data);
                });
            }
        })
    }
});

/*
  Mixin for (Array)Controllers to limit the items viewed.

  'model' should hold the complete list.
  'items' holds de items to show and is the property to use in handlebars template.
  'canLoadMore' is a boolean to determine if there are still more items to show.
  'showMore' is the event to trigger to add more items.
  'perPage' is the initial and incremental number of items to show.
  'page' tracks the current page.
 */
App.ShowMoreItemsMixin = Em.Mixin.create({
    perPage: 5,
    page: 0,
    items: Em.A(),

    remainingItemCount: function(){
        if (this.get('model.length')) {
            return this.get('model.length') - (this.get('page')  * this.get('perPage'));

        }
        return '...';
    }.property('page', 'model.isLoaded', 'perPage'),

    canLoadMore: function(){
        var totalPages = Math.ceil(this.get('model.length') / this.get('perPage'));
        return totalPages > this.get('page');
    }.property('perPage', 'page'),

    loadInitial: function(){
        // If no items have been load yet, please do.
        if (this.get('items').length == 0) {
            this.showMore();
        }
    }.observes('model.isLoaded'),

    showMore: function() {
        var start = this.get('page') * this.get('perPage');
        this.incrementProperty('page');
        var end = this.get('page') * this.get('perPage');
        this.get('items').pushObjects(this.get('model').slice(start, end));
    }

});

App.PopOverMixin = Em.Mixin.create({
   didInsertElement: function(){
        this.$('.has-popover').popover({trigger: 'hover', placement: 'left'});
        this.$('.has-tooltip').tooltip({trigger: 'hover', placement: 'right'});
   }
});

App.MapPicker = Em.View.extend({

    templateName: 'map_picker',
    marker: null,

    submit: function(e){
        e.preventDefault();
        this.lookUpLocation();
    },

    lookUpLocation: function() {
        var address = this.get('lookup');
        var view = this;
        view.geocoder.geocode( {'address': address}, function(results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                view.placeMarker(results[0].geometry.location);
                view.set('latitude',  '' + results[0].geometry.location.lat().toString());
                view.set('longitude', '' + results[0].geometry.location.lng().toString());

            } else {
                alert('Geocode was not successful for the following reason: ' + status);
            }
        });
    },
    placeMarker: function (position) {
        var view = this;
        if (view.marker) {
            view.marker.setMap(null)
        }

        view.marker = new google.maps.Marker({
            position: position,
            map: view.map
        });
        view.map.panTo(position);
    },

    didInsertElement: function(){
        var view = this;
        this.geocoder = new google.maps.Geocoder();
        var view = this;
        var point = new google.maps.LatLng(view.get('latitude'), view.get('longitude'));
        var mapOptions = {
            zoom: 6,
            center: point,
            mapTypeId: google.maps.MapTypeId.ROADMAP
          };
        view.map = new google.maps.Map(this.$('.map-picker').get(0), mapOptions);

        view.placeMarker(point);

        google.maps.event.addListener(view.map, 'click', function(e) {
            var loc = {};
            view.set('latitude', e.latLng.lat().toString());
            view.set('longitude', e.latLng.lng().toString());
            view.placeMarker(e.latLng);
        });
    }

});

<|endoftext|>


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

export default function InputBox(props) {
  return (

      {props.msg}:
      <input ref={props.ref} type="text"/>

  );
}
InputBox.propTypes = {
  key: PropTypes.number,
  msg: PropTypes.string,
  ref: PropTypes.object
};
<commit_msg>Fix input box component<commit_after>import React, { PropTypes } from 'react';

export default function InputBox(props) {
  return (

      {props.msg}
      <input className="form-control theme-form" ref={props.ref} type="text"/>

  );
}
InputBox.propTypes = {
  key: PropTypes.number,
  msg: PropTypes.string,
  ref: PropTypes.object
};
<|endoftext|>


<commit_before>import Ember from 'ember';
import { deprecate } from 'ember-data/-private/debug';

/**
  @module ember-data
*/

function getDefaultValue(record, options, key) {
  if (typeof options.defaultValue === 'function') {
    return options.defaultValue.apply(null, arguments);
  } else {
    let defaultValue = options.defaultValue;
    deprecate(`Non primitive defaultValues are deprecated because they are shared between all instances. If you would like to use a complex object as a default value please provide a function that returns the complex object.`,
      typeof defaultValue !== 'object' || defaultValue === null, {
        id: 'ds.defaultValue.complex-object',
        until: '3.0.0'
      });
    return defaultValue;
  }
}

function hasValue(record, key) {
  return key in record._attributes ||
         key in record._inFlightAttributes ||
         key in record._data;
}

function getValue(record, key) {
  if (key in record._attributes) {
    return record._attributes[key];
  } else if (key in record._inFlightAttributes) {
    return record._inFlightAttributes[key];
  } else {
    return record._data[key];
  }
}

/**
  `DS.attr` defines an attribute on a [DS.Model](/api/data/classes/DS.Model.html).
  By default, attributes are passed through as-is, however you can specify an
  optional type to have the value automatically transformed.
  Ember Data ships with four basic transform types: `string`, `number`,
  `boolean` and `date`. You can define your own transforms by subclassing
  [DS.Transform](/api/data/classes/DS.Transform.html).

  Note that you cannot use `attr` to define an attribute of `id`.

  `DS.attr` takes an optional hash as a second parameter, currently
  supported options are:

  - `defaultValue`: Pass a string or a function to be called to set the attribute
                    to a default value if none is supplied.

  Example

  ```app/models/user.js
  import DS from 'ember-data';

  export default DS.Model.extend({
    username: DS.attr('string'),
    email: DS.attr('string'),
    verified: DS.attr('boolean', { defaultValue: false })
  });
  ```

  Default value can also be a function. This is useful it you want to return
  a new object for each attribute.

  ```app/models/user.js
  import DS from 'ember-data';

  export default DS.Model.extend({
    username: attr('string'),
    email: attr('string'),
    settings: attr({defaultValue: function() {
      return {};
    }})
  });
  ```

  The `options` hash is passed as second argument to a transforms'
  `serialize` and `deserialize` method. This allows to configure a
  transformation and adapt the corresponding value, based on the config:

  ```app/models/post.js
  export default DS.Model.extend({
    text: DS.attr('text', {
      uppercase: true
    })
  });
  ```

  ```app/transforms/text.js
  export default DS.Transform.extend({
    serialize(value, options) {
      if (options.uppercase) {
        return value.toUpperCase();
      }

      return value;
    },

    deserialize(value) {
      return value;
    }
  })
  ```

  @namespace
  @method attr
  @for DS
  @param {String|Object} type the attribute type
  @param {Object} options a hash of options
  @return {Attribute}
*/

export default function attr(type, options) {
  if (typeof type === 'object') {
    options = type;
    type = undefined;
  } else {
    options = options || {};
  }

  let meta = {
    type: type,
    isAttribute: true,
    options: options
  };

  return Ember.computed({
    get(key) {
      let internalModel = this._internalModel;
      if (hasValue(internalModel, key)) {
        return getValue(internalModel, key);
      } else {
        return getDefaultValue(this, options, key);
      }
    },
    set(key, value) {
      let internalModel = this._internalModel;
      let oldValue = getValue(internalModel, key);
      let originalValue;

      if (value !== oldValue) {
        // Add the new value to the changed attributes hash; it will get deleted by
        // the 'didSetProperty' handler if it is no different from the original value
        internalModel._attributes[key] = value;

        if (key in internalModel._inFlightAttributes) {
          originalValue = internalModel._inFlightAttributes[key];
        } else {
          originalValue = internalModel._data[key];
        }

        this._internalModel.send('didSetProperty', {
          name: key,
          oldValue: oldValue,
          originalValue: originalValue,
          value: value
        });
      }

      return value;
    }
  }).meta(meta);
}
<commit_msg>[DOC] Cleanup example code in attr<commit_after>import Ember from 'ember';
import { deprecate } from 'ember-data/-private/debug';

/**
  @module ember-data
*/

function getDefaultValue(record, options, key) {
  if (typeof options.defaultValue === 'function') {
    return options.defaultValue.apply(null, arguments);
  } else {
    let defaultValue = options.defaultValue;
    deprecate(`Non primitive defaultValues are deprecated because they are shared between all instances. If you would like to use a complex object as a default value please provide a function that returns the complex object.`,
      typeof defaultValue !== 'object' || defaultValue === null, {
        id: 'ds.defaultValue.complex-object',
        until: '3.0.0'
      });
    return defaultValue;
  }
}

function hasValue(record, key) {
  return key in record._attributes ||
         key in record._inFlightAttributes ||
         key in record._data;
}

function getValue(record, key) {
  if (key in record._attributes) {
    return record._attributes[key];
  } else if (key in record._inFlightAttributes) {
    return record._inFlightAttributes[key];
  } else {
    return record._data[key];
  }
}

/**
  `DS.attr` defines an attribute on a [DS.Model](/api/data/classes/DS.Model.html).
  By default, attributes are passed through as-is, however you can specify an
  optional type to have the value automatically transformed.
  Ember Data ships with four basic transform types: `string`, `number`,
  `boolean` and `date`. You can define your own transforms by subclassing
  [DS.Transform](/api/data/classes/DS.Transform.html).

  Note that you cannot use `attr` to define an attribute of `id`.

  `DS.attr` takes an optional hash as a second parameter, currently
  supported options are:

  - `defaultValue`: Pass a string or a function to be called to set the attribute
                    to a default value if none is supplied.

  Example

  ```app/models/user.js
  import DS from 'ember-data';

  export default DS.Model.extend({
    username: DS.attr('string'),
    email: DS.attr('string'),
    verified: DS.attr('boolean', { defaultValue: false })
  });
  ```

  Default value can also be a function. This is useful it you want to return
  a new object for each attribute.

  ```app/models/user.js
  import DS from 'ember-data';

  export default DS.Model.extend({
    username: attr('string'),
    email: attr('string'),
    settings: attr({
      defaultValue() {
        return {};
      }
    })
  });
  ```

  The `options` hash is passed as second argument to a transforms'
  `serialize` and `deserialize` method. This allows to configure a
  transformation and adapt the corresponding value, based on the config:

  ```app/models/post.js
  import DS from 'ember-data';

  export default DS.Model.extend({
    text: DS.attr('text', {
      uppercase: true
    })
  });
  ```

  ```app/transforms/text.js
  import DS from 'ember-data';

  export default DS.Transform.extend({
    serialize(value, options) {
      if (options.uppercase) {
        return value.toUpperCase();
      }

      return value;
    },

    deserialize(value) {
      return value;
    }
  })
  ```

  @namespace
  @method attr
  @for DS
  @param {String|Object} type the attribute type
  @param {Object} options a hash of options
  @return {Attribute}
*/

export default function attr(type, options) {
  if (typeof type === 'object') {
    options = type;
    type = undefined;
  } else {
    options = options || {};
  }

  let meta = {
    type: type,
    isAttribute: true,
    options: options
  };

  return Ember.computed({
    get(key) {
      let internalModel = this._internalModel;
      if (hasValue(internalModel, key)) {
        return getValue(internalModel, key);
      } else {
        return getDefaultValue(this, options, key);
      }
    },
    set(key, value) {
      let internalModel = this._internalModel;
      let oldValue = getValue(internalModel, key);
      let originalValue;

      if (value !== oldValue) {
        // Add the new value to the changed attributes hash; it will get deleted by
        // the 'didSetProperty' handler if it is no different from the original value
        internalModel._attributes[key] = value;

        if (key in internalModel._inFlightAttributes) {
          originalValue = internalModel._inFlightAttributes[key];
        } else {
          originalValue = internalModel._data[key];
        }

        this._internalModel.send('didSetProperty', {
          name: key,
          oldValue: oldValue,
          originalValue: originalValue,
          value: value
        });
      }

      return value;
    }
  }).meta(meta);
}
<|endoftext|>


<commit_before>const sh = require('shelljs'),
      path = require('path'),
      fs = require('fs-extra'),
      basePackagePath = path.resolve(__dirname, '../..'),
      fixturePath = path.resolve(basePackagePath, 'spec', 'fixtures'),
      resultFixturePath = path.resolve(fixturePath, 'result'),
      readJSON = fs.readJSONSync,
      helpers = require('../helpers');

let prepareInput,
    clearInputForTest,
    runAppFromConsole;

/**
 * функия копирует входные json в корень
 * @param {string} testCase - кейс использования:
 * (equal|oneMoreError|oneMoreNewError|empty)
 */
prepareInput = (testCase) => {
  const testDataPath = helpers.identInputForTest(testCase, fixturePath);

  sh.cp(testDataPath.masterJSON, `${basePackagePath}/fromMaster.json`);
  sh.cp(testDataPath.currentJson,
    `${basePackagePath}/fromCurrent.json`);
};

/**
 * Запуск приложения
 * @param {string} [specifyTempPath] - специфичный путь для хранения результата
 */
runAppFromConsole = (specifyTempPath) => {
  console.log(`cd ${basePackagePath}; node index.js eslint -master ${basePackagePath}/fromMaster.json -current ${basePackagePath}/fromCurrent.json`);
  sh.exec(`cd ${basePackagePath}`);
  sh.exec(`node index.js eslint -master ${basePackagePath}/fromMaster.json -current ${basePackagePath}/fromCurrent.json ${specifyTempPath ? '-result ' + specifyTempPath : ''}`);
};

/**
 * Очистить входные данные после теста
 */
clearInputForTest = () => {
  const filesForRemove = [
    `${basePackagePath}/fromCurrent.json`,
    `${basePackagePath}/fromMaster.json`,
    `${basePackagePath}/result.json`
  ];

  filesForRemove.forEach((item) => {
    if (sh.test('-e', item)) {
      sh.rm(item);
    }
  });
};

describe('eslint', () => {
  let resultJSON,
      expectedJSON;

  afterEach(() => {
    clearInputForTest();
  });

  describe('исключающий мерж', () => {
    beforeAll(() => {
      sh.chmod('+x', `${basePackagePath}/index.js`);
    });

    describe('создан результирующий файл', () => {
      it('в папке от куда вызывается скрипт', () => {
        prepareInput('empty');
        runAppFromConsole();

        expect(sh.test('-f', `${basePackagePath}/result.json`)).toBeTruthy();
      });

      it('в указанной папке', () => {
        prepareInput('empty');
        runAppFromConsole(basePackagePath);

        expect(sh.test('-f', `${basePackagePath}/result.json`)).toBeTruthy();
      });
    });

    describe('файлы', function () {
      afterEach(() => {
        resultJSON = undefined;
      });

      it('пусты', function () {
        prepareInput('empty');
        runAppFromConsole();
        expectedJSON = readJSON(`${resultFixturePath}/empty.json`);
        resultJSON = readJSON(`${basePackagePath}/result.json`);

        expect(resultJSON).toEqual([]);
      });

      it('одинаковы', function () {
        let resultFixtureName = 'empty';
        prepareInput('equal');
        runAppFromConsole();

        resultJSON = readJSON(`${basePackagePath}/result.json`);
        expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);

        expect(resultJSON).toEqual(expectedJSON);
      });

      describe('не одинаковы', function () {
        it('новый файл с ошибками', () => {
          let resultFixtureName = 'newErrorsAndFiles';
          prepareInput(resultFixtureName);
          runAppFromConsole();
          expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
          resultJSON = readJSON(`${basePackagePath}/result.json`);

          expect(resultJSON).toEqual(expectedJSON);
        });

        it('новые ошибки в файле, в котором уже были ошибки', () => {
          let resultFixtureName = 'oneMoreErrorInExistErrorFile';
          prepareInput(resultFixtureName);
          runAppFromConsole();
          expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
          resultJSON = readJSON(`${basePackagePath}/result.json`);

          expect(resultJSON).toEqual(expectedJSON);
        });

        it('уменьшение количества ошибок', () => {
          let resultFixtureName = 'empty';
          prepareInput('onesLessErrorInExistErrorFile');
          runAppFromConsole();

          expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
          resultJSON = readJSON(`${basePackagePath}/result.json`);

          expect(resultJSON).toEqual(expectedJSON);
        });

        it('уменьшение количества файлов с ошибками', () => {
          let resultFixtureName = 'onesLessErrorFile';
          prepareInput('onesLessErrorFile');
          runAppFromConsole();

          expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
          resultJSON = readJSON(`${basePackagePath}/result.json`);

          expect(resultJSON).toEqual(expectedJSON);
        });
      });
    });
  });

  describe('Интерфейс модуля', () => {
    let buildFailedConditions;

    beforeEach(() => {
      buildFailedConditions = require(`${path.resolve(basePackagePath, 'index.js')}`);
    });

    it('реализует экспорт', () => {
      expect(typeof buildFailedConditions).toEqual('function');
    });

    it('релизует мерж', () => {
      let resultFixtureName = 'oneMoreErrorInExistErrorFile';

      prepareInput(resultFixtureName);
      buildFailedConditions({
        eslint: {
          currentJson: `${basePackagePath}/fromCurrent.json`,
          masterJSON: `${basePackagePath}/fromMaster.json`,
          resultJSON: `${basePackagePath}/result.json`
        }
      }).then(function () {
        expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
        resultJSON = readJSON(`${basePackagePath}/result.json`);

        expect(resultJSON).toEqual(expectedJSON);
      });
    });
  });
});
<commit_msg>chore(*): sh.cp заменён на fs.copySync<commit_after>const sh = require('shelljs'),
      path = require('path'),
      fs = require('fs-extra'),
      basePackagePath = path.resolve(__dirname, '../..'),
      fixturePath = path.resolve(basePackagePath, 'spec', 'fixtures'),
      resultFixturePath = path.resolve(fixturePath, 'result'),
      readJSON = fs.readJSONSync,
      cp = fs.copySync,
      helpers = require('../helpers');

let prepareInput,
    clearInputForTest,
    runAppFromConsole;

/**
 * функия копирует входные json в корень
 * @param {string} testCase - кейс использования:
 * (equal|oneMoreError|oneMoreNewError|empty)
 */
prepareInput = (testCase) => {
  const testDataPath = helpers.identInputForTest(testCase, fixturePath);

  cp(testDataPath.masterJSON, `${basePackagePath}/fromMaster.json`);
  cp(testDataPath.currentJson,
    `${basePackagePath}/fromCurrent.json`);
};

/**
 * Запуск приложения
 * @param {string} [specifyTempPath] - специфичный путь для хранения результата
 */
runAppFromConsole = (specifyTempPath) => {
  console.log(`cd ${basePackagePath}; node index.js eslint -master ${basePackagePath}/fromMaster.json -current ${basePackagePath}/fromCurrent.json`);
  sh.exec(`node ${basePackagePath}/index.js eslint -master ${basePackagePath}/fromMaster.json -current ${basePackagePath}/fromCurrent.json ${specifyTempPath ? '-result ' + specifyTempPath : ''}`);
};

/**
 * Очистить входные данные после теста
 */
clearInputForTest = () => {
  const filesForRemove = [
    `${basePackagePath}/fromCurrent.json`,
    `${basePackagePath}/fromMaster.json`,
    `${basePackagePath}/result.json`
  ];

  filesForRemove.forEach((item) => {
    if (sh.test('-e', item)) {
      sh.rm(item);
    }
  });
};

describe('eslint', () => {
  let resultJSON,
      expectedJSON;

  afterEach(() => {
    clearInputForTest();
  });

  describe('исключающий мерж', () => {
    beforeAll(() => {
      sh.chmod('+x', `${basePackagePath}/index.js`);
    });

    describe('создан результирующий файл', () => {
      it('в папке от куда вызывается скрипт', () => {
        prepareInput('empty');
        runAppFromConsole();

        expect(sh.test('-f', `${basePackagePath}/result.json`)).toBeTruthy();
      });

      it('в указанной папке', () => {
        prepareInput('empty');
        runAppFromConsole(basePackagePath);

        expect(sh.test('-f', `${basePackagePath}/result.json`)).toBeTruthy();
      });
    });

    describe('файлы', function () {
      afterEach(() => {
        resultJSON = undefined;
      });

      it('пусты', function () {
        prepareInput('empty');
        runAppFromConsole();
        expectedJSON = readJSON(`${resultFixturePath}/empty.json`);
        resultJSON = readJSON(`${basePackagePath}/result.json`);

        expect(resultJSON).toEqual([]);
      });

      it('одинаковы', function () {
        let resultFixtureName = 'empty';
        prepareInput('equal');
        runAppFromConsole();

        resultJSON = readJSON(`${basePackagePath}/result.json`);
        expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);

        expect(resultJSON).toEqual(expectedJSON);
      });

      describe('не одинаковы', function () {
        it('новый файл с ошибками', () => {
          let resultFixtureName = 'newErrorsAndFiles';
          prepareInput(resultFixtureName);
          runAppFromConsole();
          expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
          resultJSON = readJSON(`${basePackagePath}/result.json`);

          expect(resultJSON).toEqual(expectedJSON);
        });

        it('новые ошибки в файле, в котором уже были ошибки', () => {
          let resultFixtureName = 'oneMoreErrorInExistErrorFile';
          prepareInput(resultFixtureName);
          runAppFromConsole();
          expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
          resultJSON = readJSON(`${basePackagePath}/result.json`);

          expect(resultJSON).toEqual(expectedJSON);
        });

        it('уменьшение количества ошибок', () => {
          let resultFixtureName = 'empty';
          prepareInput('onesLessErrorInExistErrorFile');
          runAppFromConsole();

          expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
          resultJSON = readJSON(`${basePackagePath}/result.json`);

          expect(resultJSON).toEqual(expectedJSON);
        });

        it('уменьшение количества файлов с ошибками', () => {
          let resultFixtureName = 'onesLessErrorFile';
          prepareInput('onesLessErrorFile');
          runAppFromConsole();

          expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
          resultJSON = readJSON(`${basePackagePath}/result.json`);

          expect(resultJSON).toEqual(expectedJSON);
        });
      });
    });
  });

  describe('Интерфейс модуля', () => {
    let buildFailedConditions;

    beforeEach(() => {
      buildFailedConditions = require(`${path.resolve(basePackagePath, 'index.js')}`);
    });

    it('реализует экспорт', () => {
      expect(typeof buildFailedConditions).toEqual('function');
    });

    it('релизует мерж', () => {
      let resultFixtureName = 'oneMoreErrorInExistErrorFile';

      prepareInput(resultFixtureName);
      buildFailedConditions({
        eslint: {
          currentJson: `${basePackagePath}/fromCurrent.json`,
          masterJSON: `${basePackagePath}/fromMaster.json`,
          resultJSON: `${basePackagePath}/result.json`
        }
      }).then(function () {
        expectedJSON = readJSON(`${resultFixturePath}/${resultFixtureName}.json`);
        resultJSON = readJSON(`${basePackagePath}/result.json`);

        expect(resultJSON).toEqual(expectedJSON);
      });
    });
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import classnames from 'classnames';
import ScrollLock from 'react-scrolllock';

import IconBars from '../images/icon-bars.svg';
import IconTimes from '../images/icon-times.svg';
import IconTwitterSquare from '../images/icon-twitter-square.svg';
import IconFacebookSquare from '../images/icon-facebook-square.svg';

import HDOServices from './HDOServices';

export default class MainMenu extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hidden: true };
  }

  onClick = () => {
    this.setState({ hidden: !this.state.hidden });
  };

  render() {
    const { hidden } = this.state;

    const triggerClasses = classnames('main-menu__trigger', { hidden: !hidden });
    const modalClasses = classnames('main-menu__modal', { hidden });

    return (

        <button className={triggerClasses} type="button" onClick={this.onClick}>
          <IconBars aria-hidden={!hidden} />
        </button>

          {!hidden && <ScrollLock />}
          <button
            type="button"
            className="close main-menu__modal-close"
            data-dismiss="modal"
            aria-label="Close"
            onClick={this.onClick}
          >
            <IconTimes aria-hidden={hidden} />
          </button>

Holder de ord

                    Om oss

                    Støtt oss

                    <IconTwitterSquare /> Twitter

                    <IconFacebookSquare /> Facebook

Våre tjenester

              <HDOServices />

    );
  }
}
<commit_msg>Allow scrolling in modal<commit_after>import React from 'react';
import classnames from 'classnames';
import ScrollLock from 'react-scrolllock';

import IconBars from '../images/icon-bars.svg';
import IconTimes from '../images/icon-times.svg';
import IconTwitterSquare from '../images/icon-twitter-square.svg';
import IconFacebookSquare from '../images/icon-facebook-square.svg';

import HDOServices from './HDOServices';

export default class MainMenu extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hidden: true };
  }

  onClick = () => {
    this.setState({ hidden: !this.state.hidden });
  };

  render() {
    const { hidden } = this.state;

    const triggerClasses = classnames('main-menu__trigger', { hidden: !hidden });
    const modalClasses = classnames('main-menu__modal', { hidden });

    return (

        <button className={triggerClasses} type="button" onClick={this.onClick}>
          <IconBars aria-hidden={!hidden} />
        </button>

 {
            this.modalRef = el;
          }}
        >
          {!hidden && <ScrollLock touchScrollTarget={this.modalRef} />}
          <button
            type="button"
            className="close main-menu__modal-close"
            data-dismiss="modal"
            aria-label="Close"
            onClick={this.onClick}
          >
            <IconTimes aria-hidden={hidden} />
          </button>

Holder de ord

                    Om oss

                    Støtt oss

                    <IconTwitterSquare /> Twitter

                    <IconFacebookSquare /> Facebook

Våre tjenester

              <HDOServices />

    );
  }
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/swiper-container';

export default Ember.Component.extend({
  layout,
  classNames: ['swiper-container'],
  swiper: false,

  swiperOptions: Ember.computed('pagination', 'loop', 'vertical', function() {
    let options = {};

    if (this.get('pagination')) {
      options.pagination = `#${this.get('elementId')} .swiper-pagination`;
      options.paginationClickable = true;
    }

    if (this.get('navigation')) {
      options.nextButton = '.swiper-button-next';
      options.prevButton = '.swiper-button-prev';
    }

    if (this.get('loop')) {
      options.loop = true;
    }

    if (this.get('initialSlide')) {
      options.initialSlide = this.get('initialSlide');
    }

    // disables swipping
    if (this.get('followFinger')) {
      options.followFinger = false;
    }

    if (this.get('vertical')) {
      options.direction = 'vertical';
    }

    if (this.get('slidesPerView')) {
      options.slidesPerView = this.get('slidesPerView');
    }

    if (this.get('spaceBetween')) {
      options.spaceBetween = this.get('spaceBetween');
    }

    if (this.get('centered')) {
      options.centeredSlides = true;
    }

    if (this.get('freeMode')) {
      options.freeMode = true;
    }

    if (this.get('freeModeSticky')) {
      options.freeModeSticky = true;
    }

    if (this.get('grabCursor')) {
      options.grabCursor = true;
    }

    options.onSlideChangeEnd = this.slideChanged.bind(this);

    return options;
  }),

  updateTriggered: Ember.observer('updateFor', function() {
    Ember.run.once(this, this.get('swiper').update);
  }),

  forceUpdate(updateTranslate) {
    this.get('swiper').update(updateTranslate === undefined ? false : updateTranslate);
    this.get('swiper').slideTo(this.get('currentSlide'));
  },

  slideChanged(swiper) {
    let index = this.get('loop') ? Ember.$(swiper.slides).filter('.swiper-slide-active').attr('data-swiper-slide-index') : swiper.activeIndex;
    this.set('currentSlideInternal', index);
    this.set('currentSlide', index);

    if (this.get('onChange')) {
      this.sendAction('onChange', swiper.slides[swiper.activeIndex]);
    }
  },

  currentSlideModified: Ember.observer('currentSlide', function() {
    Ember.run.later(this, () => {
      if (this.get('currentSlide') !== this.get('currentSlideInternal')) {
        let index = this.get('currentSlide');

        if (this.get('loop')) {
          let swiper = this.get('swiper');
          index = Ember.$(swiper.slides).filter(`[data-swiper-slide-index=${this.get('currentSlide')}]`).prevAll().length;
        }

        this.get('swiper').slideTo(index);
        this.set('currentSlideInternal', this.get('currentSlide'));
      }
    });
  }),

  initSwiper: Ember.on('init', function() {
    Ember.run.later(this, () => {
      this.set('swiper', new Swiper(`#${this.get('elementId')}`, this.get('swiperOptions')));
      this.set('registerAs', this);
    });
  })

});
<commit_msg>adding autoplay options<commit_after>import Ember from 'ember';
import layout from '../templates/components/swiper-container';

export default Ember.Component.extend({
  layout,
  classNames: ['swiper-container'],
  swiper: false,

  swiperOptions: Ember.computed('pagination', 'loop', 'vertical', function() {
    let options = {};

    if (this.get('pagination')) {
      options.pagination = `#${this.get('elementId')} .swiper-pagination`;
      options.paginationClickable = true;
    }

    if (this.get('navigation')) {
      options.nextButton = '.swiper-button-next';
      options.prevButton = '.swiper-button-prev';
    }

    if (this.get('loop')) {
      options.loop = true;
    }

    if (this.get('initialSlide')) {
      options.initialSlide = this.get('initialSlide');
    }

    // disables swipping
    if (this.get('followFinger')) {
      options.followFinger = false;
    }

    if (this.get('vertical')) {
      options.direction = 'vertical';
    }

    if (this.get('slidesPerView')) {
      options.slidesPerView = this.get('slidesPerView');
    }

    if (this.get('spaceBetween')) {
      options.spaceBetween = this.get('spaceBetween');
    }

    if (this.get('centered')) {
      options.centeredSlides = true;
    }

    if (this.get('freeMode')) {
      options.freeMode = true;
    }

    if (this.get('freeModeSticky')) {
      options.freeModeSticky = true;
    }

    if (this.get('grabCursor')) {
      options.grabCursor = true;
    }

    if (this.get('autoplay')) {
      options.grabCursor = this.get('autoplay');
    }

    if (this.get('autoplayStopOnLast')) {
      options.grabCursor = this.get('autoplayStopOnLast');
    }

    if (this.get('autoplayDisableOnInteraction')) {
      options.grabCursor = this.get('autoplayDisableOnInteraction');
    }

    options.onSlideChangeEnd = this.slideChanged.bind(this);

    return options;
  }),

  updateTriggered: Ember.observer('updateFor', function() {
    Ember.run.once(this, this.get('swiper').update);
  }),

  forceUpdate(updateTranslate) {
    this.get('swiper').update(updateTranslate === undefined ? false : updateTranslate);
    this.get('swiper').slideTo(this.get('currentSlide'));
  },

  slideChanged(swiper) {
    let index = this.get('loop') ? Ember.$(swiper.slides).filter('.swiper-slide-active').attr('data-swiper-slide-index') : swiper.activeIndex;
    this.set('currentSlideInternal', index);
    this.set('currentSlide', index);

    if (this.get('onChange')) {
      this.sendAction('onChange', swiper.slides[swiper.activeIndex]);
    }
  },

  currentSlideModified: Ember.observer('currentSlide', function() {
    Ember.run.later(this, () => {
      if (this.get('currentSlide') !== this.get('currentSlideInternal')) {
        let index = this.get('currentSlide');

        if (this.get('loop')) {
          let swiper = this.get('swiper');
          index = Ember.$(swiper.slides).filter(`[data-swiper-slide-index=${this.get('currentSlide')}]`).prevAll().length;
        }

        this.get('swiper').slideTo(index);
        this.set('currentSlideInternal', this.get('currentSlide'));
      }
    });
  }),

  initSwiper: Ember.on('init', function() {
    Ember.run.later(this, () => {
      this.set('swiper', new Swiper(`#${this.get('elementId')}`, this.get('swiperOptions')));
      this.set('registerAs', this);
    });
  })

});
<|endoftext|>


<commit_before>const Discord = require("discord.js");
const rgbHex = require("rgb-hex");
const Skins = require("../data/skins");
const logger = require("../logger");

const filter = (reaction) => {
  return ["👍", "👎", "👏", "😔"].some((name) => reaction.emoji.name === name);
};

async function postSkin({ md5, title, dest }) {
  const skin = await Skins.getSkinByMd5(md5);
  if (skin == null) {
    console.warn("Could not find skin for md5", { md5, alert: true });
    logger.warn("Could not find skin for md5", { md5, alert: true });
    return;
  }
  const {
    canonicalFilename,
    screenshotUrl,
    skinUrl,
    webampUrl,
    averageColor,
    emails,
    tweetUrl,
    twitterLikes,
    tweetStatus,
    internetArchiveUrl,
    internetArchiveItemName,
    readmeText,
  } = skin;
  title = title ? title(canonicalFilename) : canonicalFilename;

  const embed = new Discord.RichEmbed()
    .setTitle(title)
    .addField("Try Online", `[webamp.org](${webampUrl})`, true)
    .addField("Download", `[${canonicalFilename}](${skinUrl})`, true)
    .addField("Md5", md5, true)
    .setImage(screenshotUrl);

  if (readmeText) {
    // Trim the readme since Discord will reject it otherwise.
    embed.setDescription(`\`\`\`${readmeText.slice(0, 2000)}\`\`\``);
  }
  if (averageColor) {
    try {
      const color = rgbHex(averageColor);
      if (String(color).length === 6) {
        embed.setColor(`#${color}`);
      } else {
        logger.warn("Did not get a safe color", {
          averageColor,
          color,
          warn: true,
        });
      }
    } catch (e) {
      logger.error("Could not use color", { averageColor, alert: true });
    }
  }
  if (emails != null && emails.length) {
    embed.addField("Emails", emails.join(", "), true);
  }
  if (tweetUrl != null) {
    let likes = "";
    if (twitterLikes != null) {
      likes = `(${Number(twitterLikes).toLocaleString()} likes) `;
    }
    embed.addField("Tweet Status", `[Tweeted](${tweetUrl}) ${likes}🐦`, true);
  } else {
    if (tweetStatus === "UNREVIEWED") {
      embed.setFooter("React with 👍 or 👎 to approve or deny");
    }
    embed.addField("Tweet Status", getPrettyTwitterStatus(tweetStatus), true);
  }
  if (internetArchiveUrl) {
    embed.addField(
      "Internet Archive",
      `[${internetArchiveItemName || "Permalink"}](${internetArchiveUrl})`,
      true
    );
  }

  const msg = await dest.send(embed);

  await msg.awaitReactions(filter, { max: 1 }).then(async (collected) => {
    const vote = collected.first();
    const user = vote.users.first();
    switch (vote.emoji.name) {
      case "👍":
      case "👏":
        await Skins.approve(md5);
        logger.info(`${user.username} approved ${md5}`);
        await msg.channel.send(
          `${canonicalFilename} was approved by ${user.username}`
        );
        msg.react("✅");
        break;
      case "😔":
      case "👎":
        await Skins.reject(md5);
        logger.info(`${user.username} rejected ${md5}`);
        await msg.channel.send(
          `${canonicalFilename} was rejected by ${user.username}`
        );
        msg.react("❌");
        break;
    }
  });
}

function getPrettyTwitterStatus(status) {
  switch (status) {
    case "APPROVED":
      return "Approved ✅";
    case "REJECTED":
      return "Rejected ❌";
    case "UNREVIEWED":
      return "Unreviewed ❔";
    case "TWEETED":
      return "Tweeted 🐦";
  }
}

module.exports = { postSkin };
<commit_msg>Prepopulate reactions on voting skins<commit_after>const Discord = require("discord.js");
const rgbHex = require("rgb-hex");
const Skins = require("../data/skins");
const logger = require("../logger");

function isEligableToApprove(user) {
  return !user.bot;
}

const filter = reaction => {
  const hasNonBot = reaction.users.some(isEligableToApprove);

  return (
    hasNonBot &&
    ["👍", "👎", "👏", "😔"].some(name => reaction.emoji.name === name)
  );
};

async function postSkin({ md5, title, dest }) {
  const skin = await Skins.getSkinByMd5(md5);
  if (skin == null) {
    console.warn("Could not find skin for md5", { md5, alert: true });
    logger.warn("Could not find skin for md5", { md5, alert: true });
    return;
  }
  const {
    canonicalFilename,
    screenshotUrl,
    skinUrl,
    webampUrl,
    averageColor,
    emails,
    tweetUrl,
    twitterLikes,
    tweetStatus,
    internetArchiveUrl,
    internetArchiveItemName,
    readmeText
  } = skin;
  title = title ? title(canonicalFilename) : canonicalFilename;

  const embed = new Discord.RichEmbed()
    .setTitle(title)
    .addField("Try Online", `[webamp.org](${webampUrl})`, true)
    .addField("Download", `[${canonicalFilename}](${skinUrl})`, true)
    .addField("Md5", md5, true)
    .setImage(screenshotUrl);

  if (readmeText) {
    // Trim the readme since Discord will reject it otherwise.
    embed.setDescription(`\`\`\`${readmeText.slice(0, 2000)}\`\`\``);
  }
  if (averageColor) {
    try {
      const color = rgbHex(averageColor);
      if (String(color).length === 6) {
        embed.setColor(`#${color}`);
      } else {
        logger.warn("Did not get a safe color", {
          averageColor,
          color,
          warn: true
        });
      }
    } catch (e) {
      logger.error("Could not use color", { averageColor, alert: true });
    }
  }
  if (emails != null && emails.length) {
    embed.addField("Emails", emails.join(", "), true);
  }
  if (tweetUrl != null) {
    let likes = "";
    if (twitterLikes != null) {
      likes = `(${Number(twitterLikes).toLocaleString()} likes) `;
    }
    embed.addField("Tweet Status", `[Tweeted](${tweetUrl}) ${likes}🐦`, true);
  } else {
    if (tweetStatus === "UNREVIEWED") {
      embed.setFooter("React with 👍 or 👎 to approve or deny");
    }
    embed.addField("Tweet Status", getPrettyTwitterStatus(tweetStatus), true);
  }
  if (internetArchiveUrl) {
    embed.addField(
      "Internet Archive",
      `[${internetArchiveItemName || "Permalink"}](${internetArchiveUrl})`,
      true
    );
  }

  const msg = await dest.send(embed);
  await Promise.all([msg.react("👍"), msg.react("👎")]);

  // TODO: Timeout at some point
  await msg.awaitReactions(filter, { max: 1 }).then(async collected => {
    const vote = collected.first();
    const user = vote.users.find(isEligableToApprove);
    switch (vote.emoji.name) {
      case "👍":
      case "👏":
        await Skins.approve(md5);
        logger.info(`${user.username} approved ${md5}`);
        await msg.channel.send(
          `${canonicalFilename} was approved by ${user.username}`
        );
        msg.react("✅");
        break;
      case "😔":
      case "👎":
        await Skins.reject(md5);
        logger.info(`${user.username} rejected ${md5}`);
        await msg.channel.send(
          `${canonicalFilename} was rejected by ${user.username}`
        );
        msg.react("❌");
        break;
    }
  });
}

function getPrettyTwitterStatus(status) {
  switch (status) {
    case "APPROVED":
      return "Approved ✅";
    case "REJECTED":
      return "Rejected ❌";
    case "UNREVIEWED":
      return "Unreviewed ❔";
    case "TWEETED":
      return "Tweeted 🐦";
  }
}

module.exports = { postSkin };
<|endoftext|>


<commit_before>import Ember from 'ember';
import template from "ember-hellgate/templates/view";

export default Ember.View.extend({
  template: template
});
<commit_msg>Defining a css class to the hellgate container.<commit_after>import Ember from 'ember';
import template from "ember-hellgate/templates/view";

export default Ember.View.extend({
  classNames: ['hellgate-container'],
  template: template
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Pterodactyl Daemon
 * Copyright (c) 2015 Dane Everitt <dane@daneeveritt.com>
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
const rfr = require('rfr');
const Async = require('async');
const moment = require('moment');
const _ = require('underscore');
const EventEmitter = require('events').EventEmitter;
const Querystring = require('querystring');
const Path = require('path');
const Fs = require('fs-extra');
const extendify = require('extendify');

const Log = rfr('src/helpers/logger.js');
const Docker = rfr('src/controllers/docker.js');
const Service = rfr('src/services/minecraft/index.js');
const Status = rfr('src/helpers/status.js');
const ConfigHelper = rfr('src/helpers/config.js');
const Websocket = rfr('src/http/socket.js');
const UploadServer = rfr('src/http/upload.js');
const FileSystem = rfr('src/controllers/fs.js');
const SFTPController = rfr('src/controllers/sftp.js');

const SFTP = new SFTPController();
const Config = new ConfigHelper();

class Server extends EventEmitter {

    constructor(json, next) {
        super();
        const self = this;
        this.status = Status.OFF;
        this.json = json;
        this.uuid = this.json.uuid;
        this.processData = {
            query: {},
            process: {},
        };

        this.intervals = {
            process: null,
            query: null,
        };

        this.buildInProgress = false;
        this.configLocation = Path.join(__dirname, '../../config/servers/', this.uuid, 'server.json');

        this.log = Log.child({ server: this.uuid });
        this.lastCrash = undefined;

        // @TODO: If container doesn't exist attempt to create a new container and then try again.
        // If that faisl it truly is a fatal error and we should exit.
        this.docker = new Docker(this, function constructorServer(err, status) {
            if (status === true) {
                self.log.info('Daemon detected that the server container is currently running, re-attaching to it now!');
            }
            return next(err);
        });

        this.service = new Service(this);

        this.socketIO = new Websocket(this).init();
        this.uploadSocket = new UploadServer(this).init();
        this.fs = new FileSystem(this);
    }

    hasPermission(perm, token) {
        if (typeof perm !== 'undefined' && typeof token !== 'undefined') {
            if (typeof this.json.keys !== 'undefined' && token in this.json.keys) {
                if (this.json.keys[token].indexOf(perm) > -1) {
                    return true;
                }
            }
        }
        return false;
    }

    setStatus(status) {
        if (status === this.status) return;
        const inverted = _.invert(Status);

        // If a user starts their server and then tries to stop it before the server is done
        // it triggers a crash condition. This logic determines if the server status is set to
        // stopping, and if so, we prevent changing the status to starting or on. This allows the
        // server to not crash when we press stop before it is compeltely started.
        if (this.status === Status.STOPPING && (status === Status.ON || status === Status.STARTING)) {
            this.log.debug('Recieved request to mark server as ' + inverted[status] + ' but the server is currently marked as STOPPING.');
            return;
        }

        // Handle Internal Tracking
        if (status !== Status.OFF) {
            // If an interval has not been started, start one now.
            if (this.intervals.process === null) {
                this.intervals.process = setInterval(this.process, 2000, this);
            }
        } else {
            // Server has been stopped, lets clear the interval as well as any stored
            // information about the process or query. Lets also detach the stats stream.
            clearInterval(this.intervals.process);
            this.intervals.process = null;
            this.processData.process = {};
            this.processData.query = {};
        }

        this.log.info('Server status has been changed to ' + inverted[status]);
        this.status = status;
        this.emit('is:' + inverted[status]);
        this.emit('status', status);
    }

    preflight(next) {
        // Return immediately if server is currently powered on.
        if (this.status !== Status.OFF) {
            return next(new Error('Server is already running.'));
        }
        return this.service.onPreflight(next);
    }

    start(next) {
        const self = this;
        if (this.status !== Status.OFF) {
            return next(new Error('Server is already running.'));
        }

        if (this.json.rebuild === true || this.buildInProgress === true) {
            if (this.buildInProgress !== true) {
                Async.waterfall([
                    function (callback) {
                        self.buildInProgress = true;
                        self.emit('console', '[Daemon] Your server is currently queued for a container rebuild. This should only take a few seconds, but could take a few minutes. You do not need to do anything else while this occurs. Your server will automatically continue with startup once this process is completed.');
                        callback();
                    },
                    function (callback) {
                        self.rebuild(function (err, server) {
                            return callback(err, server);
                        });
                    },
                    function (newServer, callback) {
                        newServer.start(callback);
                    },
                ], function (err) {
                    if (err) Log.error(err);
                });
            } else {
                this.emit('console', '[Daemon] Please wait while your server is being rebuilt...');
            }
            return next(new Error('Server is currently queued for a container rebuild. Your request has been accepted and will be processed once the rebuild is complete.'));
        }

        Async.series([
            function startAsyncPreflight(callback) {
                self.log.debug('Initializing for boot sequence, running preflight checks.');
                self.preflight(callback);
            },
            function startAsyncStart(callback) {
                self.docker.start(callback);
            },
            function startAsyncAttach(callback) {
                self.docker.attach(callback);
            },
        ], function startAsyncDone(err, reboot) {
            if (err) {
                self.log.error(err);
                return next(err);
            }

            if (typeof reboot !== 'undefined') {
                // @TODO: Handle the need for a reboot here.
            }
            return next();
        });
    }

    stop(next) {
        if (this.status === Status.OFF) {
            return next(new Error('Server is already stopped.'));
        }

        this.setStatus(Status.STOPPING);
        // So, technically docker sends a SIGTERM to the process when this is run.
        // This works out fine normally, however, there are times when a container might take
        // more than 10 seconds to gracefully stop, at which point docker resorts to sending
        // a SIGKILL, which, as you can imagine, isn't ideal for stopping a server.
        //
        // So, what we will do is send a stop command, and then sit there and wait
        // until the container stops because the process stopped, at which point the crash
        // detection will not fire since we set the status to STOPPING.
        this.command(this.service.object.stop, function (err) {
            return next(err);
        });
    }

    kill(next) {
        const self = this;
        if (this.status === Status.OFF) {
            return next(new Error('Server is already stopped.'));
        }

        this.setStatus(Status.STOPPING);
        this.docker.kill(function killDockerKill(err) {
            self.setStatus(Status.OFF);
            return next(err);
        });
    }

    restart(next) {
        const self = this;
        Async.series([
            function (callback) {
                if (self.status !== Status.OFF) {
                    self.once('is:OFF', function () {
                        return callback();
                    });
                    self.stop(function (err) {
                        if (err) return callback(err);
                    });
                } else {
                    return callback();
                }
            },
            function (callback) {
                self.start(function (err) {
                    return callback(err);
                });
            },
        ], function (err) {
            return next(err);
        });
    }

    /**
     * Send output from server container to websocket.
     */
    output(output) {
        if (output.replace(/[\x00-\x1F\x7F-\x9F]/g, '') === null || output.replace(/[\x00-\x1F\x7F-\x9F]/g, '') === '' || output.replace(/[\x00-\x1F\x7F-\x9F]/g, '') === ' ') {
            return;
        }
        // For now, log to console, and strip control characters from output.
        this.service.onConsole(output);
    }

    /**
     * Send command to server.
     */
    command(command, next) {
        if (this.status === Status.OFF) {
            return next(new Error('Server is currently stopped.'));
        }

        // Prevent a user sending a stop command manually from crashing the server.
        if (command.startsWith(this.service.object.stop)) {
            this.setStatus(Status.STOPPING);
        }

        this.docker.write(command, next);
    }

    /**
     * Determines if the container stream should have ended yet, if not, mark server crashed.
     */
    streamClosed() {
        const self = this;

        if (this.status === Status.OFF || this.status === Status.STOPPING) {
            this.setStatus(Status.OFF);
            return;
        }

        if (this.json.container.crashDetection === false) {
            this.setStatus(Status.OFF);
            this.log.warn('Server detected as potentially crashed but crash detection has been disabled on this server.');
            return;
        }

        this.setStatus(Status.OFF);
        this.emit('status', 'crashed');
        if (moment.isMoment(this.lastCrash)) {
            if (moment(this.lastCrash).add(60, 'seconds').isAfter(moment())) {
                this.setCrashTime();
                this.log.debug('Server detected as crashed but has crashed within the last 60 seconds, aborting reboot.');
                return;
            }
        }

        this.log.debug('Server detected as crashed... attempting reboot.');
        this.setCrashTime();

        this.start(function streamClosedStart(err) {
            if (err) self.log.fatal(err);
        });
    }

    setCrashTime() {
        this.lastCrash = moment();
    }

    path(location) {
        const dataPath = Path.join(Config.get('sftp.path', '/srv/data'), this.json.user, '/data');
        let returnPath = dataPath;

        if (typeof location !== 'undefined' && location.replace(/\s+/g, '').length > 0) {
            returnPath = Path.join(dataPath, Path.normalize(Querystring.unescape(location.replace(/\s+/g, ''))));
        }

        // Path is good, return it.
        if (returnPath.startsWith(dataPath)) {
            return returnPath;
        }
        return dataPath;
    }

    process(self) {
        if (self.status === Status.OFF) return;

        // When the server is started a stream of process data is begun
        // which is stored in Docker.procData. We wilol now access that
        // and process it.
        if (typeof self.docker.procData === 'undefined') return;

        // We need previous cycle information as well.
        if (typeof self.docker.procData.precpu_stats.cpu_usage === 'undefined') return;

        const perCoreUsage = [];
        const priorCycle = self.docker.procData.precpu_stats;
        const cycle = self.docker.procData.cpu_stats;

        const deltaTotal = cycle.cpu_usage.total_usage - priorCycle.cpu_usage.total_usage;
        const deltaSystem = cycle.system_cpu_usage - priorCycle.system_cpu_usage;
        const totalUsage = (deltaTotal / deltaSystem) * cycle.cpu_usage.percpu_usage.length * 100;

        Async.forEachOf(cycle.cpu_usage.percpu_usage, function (cpu, index, callback) {
            if (priorCycle.cpu_usage.percpu_usage !== null && index in priorCycle.cpu_usage.percpu_usage) {
                const priorCycleCpu = priorCycle.cpu_usage.percpu_usage[index];
                const deltaCore = cpu - priorCycleCpu;
                perCoreUsage.push(parseFloat(((deltaCore / deltaSystem) * cycle.cpu_usage.percpu_usage.length * 100).toFixed(3).toString()));
            }
            callback();
        }, function () {
            self.processData.process = {
                memory: {
                    total: self.docker.procData.memory_stats.usage,
                    cmax: self.docker.procData.memory_stats.max_usage,
                    amax: self.json.build.memory * 1000000,
                },
                cpu: {
                    cores: perCoreUsage,
                    total: parseFloat(totalUsage.toFixed(3).toString()),
                },
            };
            self.emit('proc', self.processData.process);
        });
    }

    // If overwrite = true (PUT request) then the JSON is simply replaced with the new object keys
    // while keeping any that are not listed. If overwrite = false (PATCH request) then only the
    // specific data keys that exist are changed or added. (see _.extend documentation).
    modifyConfig(object, overwrite, next) {
        if (typeof overwrite === 'function') {
            next = overwrite; // eslint-disable-line
            overwrite = false; // eslint-disable-line
        }
        const self = this;
        const deepExtend = extendify({
            inPlace: false,
            arrays: 'replace',
        });
        const newObject = (overwrite === true) ? _.extend(this.json, object) : deepExtend(this.json, object);

        // Ports are a pain in the butt.
        if (typeof object.build !== 'undefined') {
            _.each(object.build, function (obj, ident) {
                if (ident.endsWith('|overwrite')) {
                    const item = ident.split('|')[0];
                    self.json.build[item] = obj;
                    delete self.json.build[ident];
                }
            });
        }

        // Do a quick determination of wether or not we need to process a rebuild request for this server.
        // If so, we need to append that action to the object that we're writing.
        if (typeof object.build !== 'undefined') {
            this.log.info('New configiguration has changes to the server\'s build settings. Server has been queued for rebuild on next boot.');
            newObject.rebuild = true;
        }

        Fs.writeJson(this.configLocation, newObject, function (err) {
            if (!err) self.json = newObject;
            return next(err);
        });
    }

    rebuild(next) {
        const self = this;
        // You shouldn't really be able to make it this far without this being set,
        // but for the sake of double checking...
        if (this.buildInProgress !== true) this.buildInProgress = true;
        Async.waterfall([
            function (callback) {
                self.log.debug('Running rebuild for server...');
                self.docker.rebuild(function (err, data) {
                    return callback(err, data);
                });
            },
            function (data, callback) {
                self.log.debug('New container created successfully, updating config...');
                self.modifyConfig({
                    rebuild: false,
                    container: {
                        id: data.id.substr(0, 12),
                        image: data.image,
                    },
                }, function (err) {
                    return callback(err);
                });
            },
            function (callback) {
                const InitializeHelper = rfr('src/helpers/initialize.js').Initialize;
                const Initialize = new InitializeHelper();
                Initialize.setup(self.json, function (err) {
                    if (err) return callback(err);

                    // If we don't do this we end up continuing to use the old server
                    // object for things which causes issues since not all the functions
                    // get updated.
                    const Servers = rfr('src/helpers/initialize.js').Servers;
                    return callback(err, Servers[self.json.uuid]);
                });
            },
            function (server, callback) {
                self.buildInProgress = false;
                callback(null, server);
            },
        ], function (err, server) {
            return next(err, server);
        });
    }

    setPassword(password, next) {
        SFTP.password(this.json.user, password, function (err) {
            return next(err);
        });
    }
}

module.exports = Server;
<commit_msg>Fix config writing<commit_after>'use strict';

/**
 * Pterodactyl Daemon
 * Copyright (c) 2015 Dane Everitt <dane@daneeveritt.com>
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
const rfr = require('rfr');
const Async = require('async');
const moment = require('moment');
const _ = require('underscore');
const EventEmitter = require('events').EventEmitter;
const Querystring = require('querystring');
const Path = require('path');
const Fs = require('fs-extra');
const extendify = require('extendify');

const Log = rfr('src/helpers/logger.js');
const Docker = rfr('src/controllers/docker.js');
const Service = rfr('src/services/minecraft/index.js');
const Status = rfr('src/helpers/status.js');
const ConfigHelper = rfr('src/helpers/config.js');
const Websocket = rfr('src/http/socket.js');
const UploadServer = rfr('src/http/upload.js');
const FileSystem = rfr('src/controllers/fs.js');
const SFTPController = rfr('src/controllers/sftp.js');

const SFTP = new SFTPController();
const Config = new ConfigHelper();

class Server extends EventEmitter {

    constructor(json, next) {
        super();
        const self = this;
        this.status = Status.OFF;
        this.json = json;
        this.uuid = this.json.uuid;
        this.processData = {
            query: {},
            process: {},
        };

        this.intervals = {
            process: null,
            query: null,
        };

        this.buildInProgress = false;
        this.configLocation = Path.join(__dirname, '../../config/servers/', this.uuid, 'server.json');

        this.log = Log.child({ server: this.uuid });
        this.lastCrash = undefined;

        // @TODO: If container doesn't exist attempt to create a new container and then try again.
        // If that faisl it truly is a fatal error and we should exit.
        this.docker = new Docker(this, function constructorServer(err, status) {
            if (status === true) {
                self.log.info('Daemon detected that the server container is currently running, re-attaching to it now!');
            }
            return next(err);
        });

        this.service = new Service(this);

        this.socketIO = new Websocket(this).init();
        this.uploadSocket = new UploadServer(this).init();
        this.fs = new FileSystem(this);
    }

    hasPermission(perm, token) {
        if (typeof perm !== 'undefined' && typeof token !== 'undefined') {
            if (typeof this.json.keys !== 'undefined' && token in this.json.keys) {
                if (this.json.keys[token].indexOf(perm) > -1) {
                    return true;
                }
            }
        }
        return false;
    }

    setStatus(status) {
        if (status === this.status) return;
        const inverted = _.invert(Status);

        // If a user starts their server and then tries to stop it before the server is done
        // it triggers a crash condition. This logic determines if the server status is set to
        // stopping, and if so, we prevent changing the status to starting or on. This allows the
        // server to not crash when we press stop before it is compeltely started.
        if (this.status === Status.STOPPING && (status === Status.ON || status === Status.STARTING)) {
            this.log.debug('Recieved request to mark server as ' + inverted[status] + ' but the server is currently marked as STOPPING.');
            return;
        }

        // Handle Internal Tracking
        if (status !== Status.OFF) {
            // If an interval has not been started, start one now.
            if (this.intervals.process === null) {
                this.intervals.process = setInterval(this.process, 2000, this);
            }
        } else {
            // Server has been stopped, lets clear the interval as well as any stored
            // information about the process or query. Lets also detach the stats stream.
            clearInterval(this.intervals.process);
            this.intervals.process = null;
            this.processData.process = {};
            this.processData.query = {};
        }

        this.log.info('Server status has been changed to ' + inverted[status]);
        this.status = status;
        this.emit('is:' + inverted[status]);
        this.emit('status', status);
    }

    preflight(next) {
        // Return immediately if server is currently powered on.
        if (this.status !== Status.OFF) {
            return next(new Error('Server is already running.'));
        }
        return this.service.onPreflight(next);
    }

    start(next) {
        const self = this;
        if (this.status !== Status.OFF) {
            return next(new Error('Server is already running.'));
        }

        if (this.json.rebuild === true || this.buildInProgress === true) {
            if (this.buildInProgress !== true) {
                Async.waterfall([
                    function (callback) {
                        self.buildInProgress = true;
                        self.emit('console', '[Daemon] Your server is currently queued for a container rebuild. This should only take a few seconds, but could take a few minutes. You do not need to do anything else while this occurs. Your server will automatically continue with startup once this process is completed.');
                        callback();
                    },
                    function (callback) {
                        self.rebuild(function (err, server) {
                            return callback(err, server);
                        });
                    },
                    function (newServer, callback) {
                        newServer.start(callback);
                    },
                ], function (err) {
                    if (err) Log.error(err);
                });
            } else {
                this.emit('console', '[Daemon] Please wait while your server is being rebuilt...');
            }
            return next(new Error('Server is currently queued for a container rebuild. Your request has been accepted and will be processed once the rebuild is complete.'));
        }

        Async.series([
            function startAsyncPreflight(callback) {
                self.log.debug('Initializing for boot sequence, running preflight checks.');
                self.preflight(callback);
            },
            function startAsyncStart(callback) {
                self.docker.start(callback);
            },
            function startAsyncAttach(callback) {
                self.docker.attach(callback);
            },
        ], function startAsyncDone(err, reboot) {
            if (err) {
                self.log.error(err);
                return next(err);
            }

            if (typeof reboot !== 'undefined') {
                // @TODO: Handle the need for a reboot here.
            }
            return next();
        });
    }

    stop(next) {
        if (this.status === Status.OFF) {
            return next(new Error('Server is already stopped.'));
        }

        this.setStatus(Status.STOPPING);
        // So, technically docker sends a SIGTERM to the process when this is run.
        // This works out fine normally, however, there are times when a container might take
        // more than 10 seconds to gracefully stop, at which point docker resorts to sending
        // a SIGKILL, which, as you can imagine, isn't ideal for stopping a server.
        //
        // So, what we will do is send a stop command, and then sit there and wait
        // until the container stops because the process stopped, at which point the crash
        // detection will not fire since we set the status to STOPPING.
        this.command(this.service.object.stop, function (err) {
            return next(err);
        });
    }

    kill(next) {
        const self = this;
        if (this.status === Status.OFF) {
            return next(new Error('Server is already stopped.'));
        }

        this.setStatus(Status.STOPPING);
        this.docker.kill(function killDockerKill(err) {
            self.setStatus(Status.OFF);
            return next(err);
        });
    }

    restart(next) {
        const self = this;
        Async.series([
            function (callback) {
                if (self.status !== Status.OFF) {
                    self.once('is:OFF', function () {
                        return callback();
                    });
                    self.stop(function (err) {
                        if (err) return callback(err);
                    });
                } else {
                    return callback();
                }
            },
            function (callback) {
                self.start(function (err) {
                    return callback(err);
                });
            },
        ], function (err) {
            return next(err);
        });
    }

    /**
     * Send output from server container to websocket.
     */
    output(output) {
        if (output.replace(/[\x00-\x1F\x7F-\x9F]/g, '') === null || output.replace(/[\x00-\x1F\x7F-\x9F]/g, '') === '' || output.replace(/[\x00-\x1F\x7F-\x9F]/g, '') === ' ') {
            return;
        }
        // For now, log to console, and strip control characters from output.
        this.service.onConsole(output);
    }

    /**
     * Send command to server.
     */
    command(command, next) {
        if (this.status === Status.OFF) {
            return next(new Error('Server is currently stopped.'));
        }

        // Prevent a user sending a stop command manually from crashing the server.
        if (command.startsWith(this.service.object.stop)) {
            this.setStatus(Status.STOPPING);
        }

        this.docker.write(command, next);
    }

    /**
     * Determines if the container stream should have ended yet, if not, mark server crashed.
     */
    streamClosed() {
        const self = this;

        if (this.status === Status.OFF || this.status === Status.STOPPING) {
            this.setStatus(Status.OFF);
            return;
        }

        if (this.json.container.crashDetection === false) {
            this.setStatus(Status.OFF);
            this.log.warn('Server detected as potentially crashed but crash detection has been disabled on this server.');
            return;
        }

        this.setStatus(Status.OFF);
        this.emit('status', 'crashed');
        if (moment.isMoment(this.lastCrash)) {
            if (moment(this.lastCrash).add(60, 'seconds').isAfter(moment())) {
                this.setCrashTime();
                this.log.debug('Server detected as crashed but has crashed within the last 60 seconds, aborting reboot.');
                return;
            }
        }

        this.log.debug('Server detected as crashed... attempting reboot.');
        this.setCrashTime();

        this.start(function streamClosedStart(err) {
            if (err) self.log.fatal(err);
        });
    }

    setCrashTime() {
        this.lastCrash = moment();
    }

    path(location) {
        const dataPath = Path.join(Config.get('sftp.path', '/srv/data'), this.json.user, '/data');
        let returnPath = dataPath;

        if (typeof location !== 'undefined' && location.replace(/\s+/g, '').length > 0) {
            returnPath = Path.join(dataPath, Path.normalize(Querystring.unescape(location.replace(/\s+/g, ''))));
        }

        // Path is good, return it.
        if (returnPath.startsWith(dataPath)) {
            return returnPath;
        }
        return dataPath;
    }

    process(self) {
        if (self.status === Status.OFF) return;

        // When the server is started a stream of process data is begun
        // which is stored in Docker.procData. We wilol now access that
        // and process it.
        if (typeof self.docker.procData === 'undefined') return;

        // We need previous cycle information as well.
        if (typeof self.docker.procData.precpu_stats.cpu_usage === 'undefined') return;

        const perCoreUsage = [];
        const priorCycle = self.docker.procData.precpu_stats;
        const cycle = self.docker.procData.cpu_stats;

        const deltaTotal = cycle.cpu_usage.total_usage - priorCycle.cpu_usage.total_usage;
        const deltaSystem = cycle.system_cpu_usage - priorCycle.system_cpu_usage;
        const totalUsage = (deltaTotal / deltaSystem) * cycle.cpu_usage.percpu_usage.length * 100;

        Async.forEachOf(cycle.cpu_usage.percpu_usage, function (cpu, index, callback) {
            if (priorCycle.cpu_usage.percpu_usage !== null && index in priorCycle.cpu_usage.percpu_usage) {
                const priorCycleCpu = priorCycle.cpu_usage.percpu_usage[index];
                const deltaCore = cpu - priorCycleCpu;
                perCoreUsage.push(parseFloat(((deltaCore / deltaSystem) * cycle.cpu_usage.percpu_usage.length * 100).toFixed(3).toString()));
            }
            callback();
        }, function () {
            self.processData.process = {
                memory: {
                    total: self.docker.procData.memory_stats.usage,
                    cmax: self.docker.procData.memory_stats.max_usage,
                    amax: self.json.build.memory * 1000000,
                },
                cpu: {
                    cores: perCoreUsage,
                    total: parseFloat(totalUsage.toFixed(3).toString()),
                },
            };
            self.emit('proc', self.processData.process);
        });
    }

    // If overwrite = true (PUT request) then the JSON is simply replaced with the new object keys
    // while keeping any that are not listed. If overwrite = false (PATCH request) then only the
    // specific data keys that exist are changed or added. (see _.extend documentation).
    modifyConfig(object, overwrite, next) {
        if (typeof overwrite === 'function') {
            next = overwrite; // eslint-disable-line
            overwrite = false; // eslint-disable-line
        }
        const self = this;
        const deepExtend = extendify({
            inPlace: false,
            arrays: 'replace',
        });
        const newObject = (overwrite === true) ? _.extend(this.json, object) : deepExtend(this.json, object);

        // Ports are a pain in the butt.
        if (typeof newObject.build !== 'undefined') {
            _.each(newObject.build, function (obj, ident) {
                if (ident.endsWith('|overwrite')) {
                    const item = ident.split('|')[0];
                    newObject.build[item] = obj;
                    delete newObject.build[ident];
                }
            });
        }

        // Do a quick determination of wether or not we need to process a rebuild request for this server.
        // If so, we need to append that action to the object that we're writing.
        if (typeof object.build !== 'undefined') {
            this.log.info('New configiguration has changes to the server\'s build settings. Server has been queued for rebuild on next boot.');
            newObject.rebuild = true;
        }

        Fs.writeJson(this.configLocation, newObject, function (err) {
            if (!err) self.json = newObject;
            return next(err);
        });
    }

    rebuild(next) {
        const self = this;
        // You shouldn't really be able to make it this far without this being set,
        // but for the sake of double checking...
        if (this.buildInProgress !== true) this.buildInProgress = true;
        Async.waterfall([
            function (callback) {
                self.log.debug('Running rebuild for server...');
                self.docker.rebuild(function (err, data) {
                    return callback(err, data);
                });
            },
            function (data, callback) {
                self.log.debug('New container created successfully, updating config...');
                self.modifyConfig({
                    rebuild: false,
                    container: {
                        id: data.id.substr(0, 12),
                        image: data.image,
                    },
                }, function (err) {
                    return callback(err);
                });
            },
            function (callback) {
                const InitializeHelper = rfr('src/helpers/initialize.js').Initialize;
                const Initialize = new InitializeHelper();
                Initialize.setup(self.json, function (err) {
                    if (err) return callback(err);

                    // If we don't do this we end up continuing to use the old server
                    // object for things which causes issues since not all the functions
                    // get updated.
                    const Servers = rfr('src/helpers/initialize.js').Servers;
                    return callback(err, Servers[self.json.uuid]);
                });
            },
            function (server, callback) {
                self.buildInProgress = false;
                callback(null, server);
            },
        ], function (err, server) {
            return next(err, server);
        });
    }

    setPassword(password, next) {
        SFTP.password(this.json.user, password, function (err) {
            return next(err);
        });
    }
}

module.exports = Server;
<|endoftext|>