<commit_before>import RestModel from 'discourse/models/rest';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import ActionSummary from 'discourse/models/action-summary';
import { url, fmt, propertyEqual } from 'discourse/lib/computed';
import Quote from 'discourse/lib/quote';
import computed from 'ember-addons/ember-computed-decorators';

const Post = RestModel.extend({

  init() {
    this.set('replyHistory', []);
  },

  shareUrl: function() {
    const user = Discourse.User.current();
    const userSuffix = user ? '?u=' + user.get('username_lower') : '';

    if (this.get('firstPost')) {
      return this.get('topic.url') + userSuffix;
    } else {
      return this.get('url') + userSuffix ;
    }
  }.property('url'),

  new_user: Em.computed.equal('trust_level', 0),
  firstPost: Em.computed.equal('post_number', 1),

  // Posts can show up as deleted if the topic is deleted
  deletedViaTopic: Em.computed.and('firstPost', 'topic.deleted_at'),
  deleted: Em.computed.or('deleted_at', 'deletedViaTopic'),
  notDeleted: Em.computed.not('deleted'),
  userDeleted: Em.computed.empty('user_id'),

  hasTimeGap: function() {
    return (this.get('daysSincePrevious') || 0) > Discourse.SiteSettings.show_time_gap_days;
  }.property('daysSincePrevious'),

  showName: function() {
    const name = this.get('name');
    return name && (name !== this.get('username'))  && Discourse.SiteSettings.display_name_on_posts;
  }.property('name', 'username'),

  postDeletedBy: function() {
    if (this.get('firstPost')) { return this.get('topic.deleted_by'); }
    return this.get('deleted_by');
  }.property('firstPost', 'deleted_by', 'topic.deleted_by'),

  postDeletedAt: function() {
    if (this.get('firstPost')) { return this.get('topic.deleted_at'); }
    return this.get('deleted_at');
  }.property('firstPost', 'deleted_at', 'topic.deleted_at'),

  url: function() {
    return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number'));
  }.property('post_number', 'topic_id', 'topic.slug'),

  // Don't drop the /1
  @computed('post_number', 'url')
  urlWithNumber(postNumber, postUrl) {
    return postNumber === 1 ? postUrl + "/1" : postUrl;
  },

  usernameUrl: url('username', '/users/%@'),

  showUserReplyTab: function() {
    return this.get('reply_to_user') && (
      !Discourse.SiteSettings.suppress_reply_directly_above ||
      this.get('reply_to_post_number') < (this.get('post_number') - 1)
    );
  }.property('reply_to_user', 'reply_to_post_number', 'post_number'),

  topicOwner: propertyEqual('topic.details.created_by.id', 'user_id'),
  hasHistory: Em.computed.gt('version', 1),
  postElementId: fmt('post_number', 'post_%@'),

  canViewRawEmail: function() {
    return this.get("user_id") === Discourse.User.currentProp("id") || Discourse.User.currentProp('staff');
  }.property("user_id"),

  wikiChanged: function() {
    const data = { wiki: this.get("wiki") };
    this._updatePost("wiki", data);
  }.observes('wiki'),

  postTypeChanged: function () {
    const data = { post_type: this.get("post_type") };
    this._updatePost("post_type", data);
  }.observes("post_type"),

  _updatePost(field, data) {
    const self = this;
    Discourse.ajax("/posts/" + this.get("id") + "/" + field, {
      type: "PUT",
      data: data
    }).then(function () {
      self.incrementProperty("version");
    }).catch(popupAjaxError);
  },

  internalLinks: function() {
    if (Ember.isEmpty(this.get('link_counts'))) return null;
    return this.get('link_counts').filterProperty('internal').filterProperty('title');
  }.property('link_counts.@each.internal'),

  // Edits are the version - 1, so version 2 = 1 edit
  editCount: function() { return this.get('version') - 1; }.property('version'),

  flagsAvailable: function() {
    const post = this;
    return Discourse.Site.currentProp('flagTypes').filter(function(item) {
      return post.get("actionByName." + item.get('name_key') + ".can_act");
    });
  }.property('actions_summary.@each.can_act'),

  actionsWithoutLikes: function() {
    if (!!Ember.isEmpty(this.get('actions_summary'))) return null;

    return this.get('actions_summary').filter(function(i) {
      if (i.get('count') === 0) return false;
      if (i.get('actionType.name_key') === 'like') { return false; }
      if (i.get('users') && i.get('users').length > 0) return true;
      return !i.get('hidden');
    });
  }.property('actions_summary.@each.users', 'actions_summary.@each.count'),

  afterUpdate(res) {
    if (res.category) {
      Discourse.Site.current().updateCategory(res.category);
    }
  },

  updateProperties() {
    return {
      post: { raw: this.get('raw'), edit_reason: this.get('editReason') },
      image_sizes: this.get('imageSizes')
    };
  },

  createProperties() {
    const data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
    data.reply_to_post_number = this.get('reply_to_post_number');
    data.image_sizes = this.get('imageSizes');

    const metaData = this.get('metaData');

    // Put the metaData into the request
    if (metaData) {
      data.meta_data = {};
      Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); });
    }

    return data;
  },

  // Expands the first post's content, if embedded and shortened.
  expand() {
    const self = this;
    return Discourse.ajax("/posts/" + this.get('id') + "/expand-embed").then(function(post) {
      self.set('cooked', "
" + post.cooked + "
" );
    });
  },

  // Recover a deleted post
  recover() {
    const post = this;
    post.setProperties({
      deleted_at: null,
      deleted_by: null,
      user_deleted: false,
      can_delete: false
    });

    return Discourse.ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false }).then(function(data){
      post.setProperties({
        cooked: data.cooked,
        raw: data.raw,
        user_deleted: false,
        can_delete: true,
        version: data.version
      });
    });
  },

  /**
    Changes the state of the post to be deleted. Does not call the server, that should be
    done elsewhere.
  **/
  setDeletedState(deletedBy) {
    this.set('oldCooked', this.get('cooked'));

    // Moderators can delete posts. Users can only trigger a deleted at message, unless delete_removed_posts_after is 0.
    if (deletedBy.get('staff') || Discourse.SiteSettings.delete_removed_posts_after === 0) {
      this.setProperties({
        deleted_at: new Date(),
        deleted_by: deletedBy,
        can_delete: false
      });
    } else {
      this.setProperties({
        cooked: Discourse.Markdown.cook(I18n.t("post.deleted_by_author", {count: Discourse.SiteSettings.delete_removed_posts_after})),
        can_delete: false,
        version: this.get('version') + 1,
        can_recover: true,
        can_edit: false,
        user_deleted: true
      });
    }
  },

  /**
    Changes the state of the post to NOT be deleted. Does not call the server.
    This can only be called after setDeletedState was called, but the delete
    failed on the server.
  **/
  undoDeleteState() {
    if (this.get('oldCooked')) {
      this.setProperties({
        deleted_at: null,
        deleted_by: null,
        cooked: this.get('oldCooked'),
        version: this.get('version') - 1,
        can_recover: false,
        user_deleted: false
      });
    }
  },

  destroy(deletedBy) {
    this.setDeletedState(deletedBy);
    return Discourse.ajax("/posts/" + this.get('id'), {
      data: { context: window.location.pathname },
      type: 'DELETE'
    });
  },

  /**
    Updates a post from another's attributes. This will normally happen when a post is loading but
    is already found in an identity map.
  **/
  updateFromPost(otherPost) {
    const self = this;
    Object.keys(otherPost).forEach(function (key) {
      let value = otherPost[key],
          oldValue = self[key];

      if (key === "replyHistory") {
        return;
      }

      if (!value) { value = null; }
      if (!oldValue) { oldValue = null; }

      let skip = false;
      if (typeof value !== "function" && oldValue !== value) {
        // wishing for an identity map
        if (key === "reply_to_user" && value && oldValue) {
          skip = value.username === oldValue.username || Em.get(value, "username") === Em.get(oldValue, "username");
        }

        if (!skip) {
          self.set(key, value);
        }
      }
    });
  },

  // Load replies to this post
  loadReplies() {
    if(this.get('loadingReplies')){
      return;
    }

    this.set('loadingReplies', true);
    this.set('replies', []);

    const self = this;
    return Discourse.ajax("/posts/" + (this.get('id')) + "/replies")
      .then(function(loaded) {
        const replies = self.get('replies');
        _.each(loaded,function(reply) {
          const post = Discourse.Post.create(reply);
          post.set('topic', self.get('topic'));
          replies.pushObject(post);
        });
      })
      ['finally'](function(){
        self.set('loadingReplies', false);
    });
  },

  // Whether to show replies directly below
  showRepliesBelow: function() {
    const replyCount = this.get('reply_count');

    // We don't show replies if there aren't any
    if (replyCount === 0) return false;

    // Always show replies if the setting `suppress_reply_directly_below` is false.
    if (!Discourse.SiteSettings.suppress_reply_directly_below) return true;

    // Always show replies if there's more than one
    if (replyCount > 1) return true;

    // If we have *exactly* one reply, we have to consider if it's directly below us
    const topic = this.get('topic');
    return !topic.isReplyDirectlyBelow(this);

  }.property('reply_count'),

  expandHidden() {
    const self = this;
    return Discourse.ajax("/posts/" + this.get('id') + "/cooked.json").then(function (result) {
      self.setProperties({
        cooked: result.cooked,
        cooked_hidden: false
      });
    });
  },

  rebake() {
    return Discourse.ajax("/posts/" + this.get("id") + "/rebake", { type: "PUT" });
  },

  unhide() {
    return Discourse.ajax("/posts/" + this.get("id") + "/unhide", { type: "PUT" });
  },

  toggleBookmark() {
    const self = this;
    let bookmarkedTopic;

    this.toggleProperty("bookmarked");

    if(this.get("bookmarked") && !this.get("topic.bookmarked")) {
      this.set("topic.bookmarked", true);
      bookmarkedTopic = true;
    }

    // need to wait to hear back from server (stuff may not be loaded)

    return Discourse.Post.updateBookmark(this.get('id'), this.get('bookmarked'))
      .then(function(result){
        self.set("topic.bookmarked", result.topic_bookmarked);
      })
      .catch(function(e) {
        self.toggleProperty("bookmarked");
        if (bookmarkedTopic) {self.set("topic.bookmarked", false); }
        throw e;
      });
  },

  updateActionsSummary(json) {
    if (json && json.id === this.get('id')) {
      json = Post.munge(json);
      this.set('actions_summary', json.actions_summary);
    }
  }

});

Post.reopenClass({

  munge(json) {
    if (json.actions_summary) {
      const lookup = Em.Object.create();

      // this area should be optimized, it is creating way too many objects per post
      json.actions_summary = json.actions_summary.map(function(a) {
        a.actionType = Discourse.Site.current().postActionTypeById(a.id);
        a.count = a.count || 0;
        const actionSummary = ActionSummary.create(a);
        lookup[a.actionType.name_key] = actionSummary;

        if (a.actionType.name_key === "like") {
          json.likeAction = actionSummary;
        }
        return actionSummary;
      });

      json.actionByName = lookup;
    }

    if (json && json.reply_to_user) {
      json.reply_to_user = Discourse.User.create(json.reply_to_user);
    }
    return json;
  },

  updateBookmark(postId, bookmarked) {
    return Discourse.ajax("/posts/" + postId + "/bookmark", {
      type: 'PUT',
      data: { bookmarked: bookmarked }
    });
  },

  deleteMany(selectedPosts, selectedReplies) {
    return Discourse.ajax("/posts/destroy_many", {
      type: 'DELETE',
      data: {
        post_ids: selectedPosts.map(function(p) { return p.get('id'); }),
        reply_post_ids: selectedReplies.map(function(p) { return p.get('id'); })
      }
    });
  },

  loadRevision(postId, version) {
    return Discourse.ajax("/posts/" + postId + "/revisions/" + version + ".json").then(function (result) {
      return Ember.Object.create(result);
    });
  },

  hideRevision(postId, version) {
    return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/hide", { type: 'PUT' });
  },

  showRevision(postId, version) {
    return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/show", { type: 'PUT' });
  },

  loadQuote(postId) {
    return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
      const post = Discourse.Post.create(result);
      return Quote.build(post, post.get('raw'), {raw: true, full: true});
    });
  },

  loadRawEmail(postId) {
    return Discourse.ajax("/posts/" + postId + "/raw-email").then(function (result) {
      return result.raw_email;
    });
  }

});

export default Post;
<commit_msg>FIX: undoDeleteState() should restore delete button.<commit_after>import RestModel from 'discourse/models/rest';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import ActionSummary from 'discourse/models/action-summary';
import { url, fmt, propertyEqual } from 'discourse/lib/computed';
import Quote from 'discourse/lib/quote';
import computed from 'ember-addons/ember-computed-decorators';

const Post = RestModel.extend({

  init() {
    this.set('replyHistory', []);
  },

  shareUrl: function() {
    const user = Discourse.User.current();
    const userSuffix = user ? '?u=' + user.get('username_lower') : '';

    if (this.get('firstPost')) {
      return this.get('topic.url') + userSuffix;
    } else {
      return this.get('url') + userSuffix ;
    }
  }.property('url'),

  new_user: Em.computed.equal('trust_level', 0),
  firstPost: Em.computed.equal('post_number', 1),

  // Posts can show up as deleted if the topic is deleted
  deletedViaTopic: Em.computed.and('firstPost', 'topic.deleted_at'),
  deleted: Em.computed.or('deleted_at', 'deletedViaTopic'),
  notDeleted: Em.computed.not('deleted'),
  userDeleted: Em.computed.empty('user_id'),

  hasTimeGap: function() {
    return (this.get('daysSincePrevious') || 0) > Discourse.SiteSettings.show_time_gap_days;
  }.property('daysSincePrevious'),

  showName: function() {
    const name = this.get('name');
    return name && (name !== this.get('username'))  && Discourse.SiteSettings.display_name_on_posts;
  }.property('name', 'username'),

  postDeletedBy: function() {
    if (this.get('firstPost')) { return this.get('topic.deleted_by'); }
    return this.get('deleted_by');
  }.property('firstPost', 'deleted_by', 'topic.deleted_by'),

  postDeletedAt: function() {
    if (this.get('firstPost')) { return this.get('topic.deleted_at'); }
    return this.get('deleted_at');
  }.property('firstPost', 'deleted_at', 'topic.deleted_at'),

  url: function() {
    return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number'));
  }.property('post_number', 'topic_id', 'topic.slug'),

  // Don't drop the /1
  @computed('post_number', 'url')
  urlWithNumber(postNumber, postUrl) {
    return postNumber === 1 ? postUrl + "/1" : postUrl;
  },

  usernameUrl: url('username', '/users/%@'),

  showUserReplyTab: function() {
    return this.get('reply_to_user') && (
      !Discourse.SiteSettings.suppress_reply_directly_above ||
      this.get('reply_to_post_number') < (this.get('post_number') - 1)
    );
  }.property('reply_to_user', 'reply_to_post_number', 'post_number'),

  topicOwner: propertyEqual('topic.details.created_by.id', 'user_id'),
  hasHistory: Em.computed.gt('version', 1),
  postElementId: fmt('post_number', 'post_%@'),

  canViewRawEmail: function() {
    return this.get("user_id") === Discourse.User.currentProp("id") || Discourse.User.currentProp('staff');
  }.property("user_id"),

  wikiChanged: function() {
    const data = { wiki: this.get("wiki") };
    this._updatePost("wiki", data);
  }.observes('wiki'),

  postTypeChanged: function () {
    const data = { post_type: this.get("post_type") };
    this._updatePost("post_type", data);
  }.observes("post_type"),

  _updatePost(field, data) {
    const self = this;
    Discourse.ajax("/posts/" + this.get("id") + "/" + field, {
      type: "PUT",
      data: data
    }).then(function () {
      self.incrementProperty("version");
    }).catch(popupAjaxError);
  },

  internalLinks: function() {
    if (Ember.isEmpty(this.get('link_counts'))) return null;
    return this.get('link_counts').filterProperty('internal').filterProperty('title');
  }.property('link_counts.@each.internal'),

  // Edits are the version - 1, so version 2 = 1 edit
  editCount: function() { return this.get('version') - 1; }.property('version'),

  flagsAvailable: function() {
    const post = this;
    return Discourse.Site.currentProp('flagTypes').filter(function(item) {
      return post.get("actionByName." + item.get('name_key') + ".can_act");
    });
  }.property('actions_summary.@each.can_act'),

  actionsWithoutLikes: function() {
    if (!!Ember.isEmpty(this.get('actions_summary'))) return null;

    return this.get('actions_summary').filter(function(i) {
      if (i.get('count') === 0) return false;
      if (i.get('actionType.name_key') === 'like') { return false; }
      if (i.get('users') && i.get('users').length > 0) return true;
      return !i.get('hidden');
    });
  }.property('actions_summary.@each.users', 'actions_summary.@each.count'),

  afterUpdate(res) {
    if (res.category) {
      Discourse.Site.current().updateCategory(res.category);
    }
  },

  updateProperties() {
    return {
      post: { raw: this.get('raw'), edit_reason: this.get('editReason') },
      image_sizes: this.get('imageSizes')
    };
  },

  createProperties() {
    const data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
    data.reply_to_post_number = this.get('reply_to_post_number');
    data.image_sizes = this.get('imageSizes');

    const metaData = this.get('metaData');

    // Put the metaData into the request
    if (metaData) {
      data.meta_data = {};
      Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); });
    }

    return data;
  },

  // Expands the first post's content, if embedded and shortened.
  expand() {
    const self = this;
    return Discourse.ajax("/posts/" + this.get('id') + "/expand-embed").then(function(post) {
      self.set('cooked', "
" + post.cooked + "
" );
    });
  },

  // Recover a deleted post
  recover() {
    const post = this;
    post.setProperties({
      deleted_at: null,
      deleted_by: null,
      user_deleted: false,
      can_delete: false
    });

    return Discourse.ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false }).then(function(data){
      post.setProperties({
        cooked: data.cooked,
        raw: data.raw,
        user_deleted: false,
        can_delete: true,
        version: data.version
      });
    });
  },

  /**
    Changes the state of the post to be deleted. Does not call the server, that should be
    done elsewhere.
  **/
  setDeletedState(deletedBy) {
    this.set('oldCooked', this.get('cooked'));

    // Moderators can delete posts. Users can only trigger a deleted at message, unless delete_removed_posts_after is 0.
    if (deletedBy.get('staff') || Discourse.SiteSettings.delete_removed_posts_after === 0) {
      this.setProperties({
        deleted_at: new Date(),
        deleted_by: deletedBy,
        can_delete: false
      });
    } else {
      this.setProperties({
        cooked: Discourse.Markdown.cook(I18n.t("post.deleted_by_author", {count: Discourse.SiteSettings.delete_removed_posts_after})),
        can_delete: false,
        version: this.get('version') + 1,
        can_recover: true,
        can_edit: false,
        user_deleted: true
      });
    }
  },

  /**
    Changes the state of the post to NOT be deleted. Does not call the server.
    This can only be called after setDeletedState was called, but the delete
    failed on the server.
  **/
  undoDeleteState() {
    if (this.get('oldCooked')) {
      this.setProperties({
        deleted_at: null,
        deleted_by: null,
        cooked: this.get('oldCooked'),
        version: this.get('version') - 1,
        can_recover: false,
        can_delete: true,
        user_deleted: false
      });
    }
  },

  destroy(deletedBy) {
    this.setDeletedState(deletedBy);
    return Discourse.ajax("/posts/" + this.get('id'), {
      data: { context: window.location.pathname },
      type: 'DELETE'
    });
  },

  /**
    Updates a post from another's attributes. This will normally happen when a post is loading but
    is already found in an identity map.
  **/
  updateFromPost(otherPost) {
    const self = this;
    Object.keys(otherPost).forEach(function (key) {
      let value = otherPost[key],
          oldValue = self[key];

      if (key === "replyHistory") {
        return;
      }

      if (!value) { value = null; }
      if (!oldValue) { oldValue = null; }

      let skip = false;
      if (typeof value !== "function" && oldValue !== value) {
        // wishing for an identity map
        if (key === "reply_to_user" && value && oldValue) {
          skip = value.username === oldValue.username || Em.get(value, "username") === Em.get(oldValue, "username");
        }

        if (!skip) {
          self.set(key, value);
        }
      }
    });
  },

  // Load replies to this post
  loadReplies() {
    if(this.get('loadingReplies')){
      return;
    }

    this.set('loadingReplies', true);
    this.set('replies', []);

    const self = this;
    return Discourse.ajax("/posts/" + (this.get('id')) + "/replies")
      .then(function(loaded) {
        const replies = self.get('replies');
        _.each(loaded,function(reply) {
          const post = Discourse.Post.create(reply);
          post.set('topic', self.get('topic'));
          replies.pushObject(post);
        });
      })
      ['finally'](function(){
        self.set('loadingReplies', false);
    });
  },

  // Whether to show replies directly below
  showRepliesBelow: function() {
    const replyCount = this.get('reply_count');

    // We don't show replies if there aren't any
    if (replyCount === 0) return false;

    // Always show replies if the setting `suppress_reply_directly_below` is false.
    if (!Discourse.SiteSettings.suppress_reply_directly_below) return true;

    // Always show replies if there's more than one
    if (replyCount > 1) return true;

    // If we have *exactly* one reply, we have to consider if it's directly below us
    const topic = this.get('topic');
    return !topic.isReplyDirectlyBelow(this);

  }.property('reply_count'),

  expandHidden() {
    const self = this;
    return Discourse.ajax("/posts/" + this.get('id') + "/cooked.json").then(function (result) {
      self.setProperties({
        cooked: result.cooked,
        cooked_hidden: false
      });
    });
  },

  rebake() {
    return Discourse.ajax("/posts/" + this.get("id") + "/rebake", { type: "PUT" });
  },

  unhide() {
    return Discourse.ajax("/posts/" + this.get("id") + "/unhide", { type: "PUT" });
  },

  toggleBookmark() {
    const self = this;
    let bookmarkedTopic;

    this.toggleProperty("bookmarked");

    if(this.get("bookmarked") && !this.get("topic.bookmarked")) {
      this.set("topic.bookmarked", true);
      bookmarkedTopic = true;
    }

    // need to wait to hear back from server (stuff may not be loaded)

    return Discourse.Post.updateBookmark(this.get('id'), this.get('bookmarked'))
      .then(function(result){
        self.set("topic.bookmarked", result.topic_bookmarked);
      })
      .catch(function(e) {
        self.toggleProperty("bookmarked");
        if (bookmarkedTopic) {self.set("topic.bookmarked", false); }
        throw e;
      });
  },

  updateActionsSummary(json) {
    if (json && json.id === this.get('id')) {
      json = Post.munge(json);
      this.set('actions_summary', json.actions_summary);
    }
  }

});

Post.reopenClass({

  munge(json) {
    if (json.actions_summary) {
      const lookup = Em.Object.create();

      // this area should be optimized, it is creating way too many objects per post
      json.actions_summary = json.actions_summary.map(function(a) {
        a.actionType = Discourse.Site.current().postActionTypeById(a.id);
        a.count = a.count || 0;
        const actionSummary = ActionSummary.create(a);
        lookup[a.actionType.name_key] = actionSummary;

        if (a.actionType.name_key === "like") {
          json.likeAction = actionSummary;
        }
        return actionSummary;
      });

      json.actionByName = lookup;
    }

    if (json && json.reply_to_user) {
      json.reply_to_user = Discourse.User.create(json.reply_to_user);
    }
    return json;
  },

  updateBookmark(postId, bookmarked) {
    return Discourse.ajax("/posts/" + postId + "/bookmark", {
      type: 'PUT',
      data: { bookmarked: bookmarked }
    });
  },

  deleteMany(selectedPosts, selectedReplies) {
    return Discourse.ajax("/posts/destroy_many", {
      type: 'DELETE',
      data: {
        post_ids: selectedPosts.map(function(p) { return p.get('id'); }),
        reply_post_ids: selectedReplies.map(function(p) { return p.get('id'); })
      }
    });
  },

  loadRevision(postId, version) {
    return Discourse.ajax("/posts/" + postId + "/revisions/" + version + ".json").then(function (result) {
      return Ember.Object.create(result);
    });
  },

  hideRevision(postId, version) {
    return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/hide", { type: 'PUT' });
  },

  showRevision(postId, version) {
    return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/show", { type: 'PUT' });
  },

  loadQuote(postId) {
    return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
      const post = Discourse.Post.create(result);
      return Quote.build(post, post.get('raw'), {raw: true, full: true});
    });
  },

  loadRawEmail(postId) {
    return Discourse.ajax("/posts/" + postId + "/raw-email").then(function (result) {
      return result.raw_email;
    });
  }

});

export default Post;
<|endoftext|>


<commit_before>// Control-flow utilities.
var cadence = require('cadence')
var assert = require('assert')

// An evented message queue.
var Procession = require('procession')

// Evented stream reading and writing.
var Staccato = require('staccato')

// JSON for use in packets.
var Jacket = require('nascent.jacket')

// Orderly destruction of complicated objects.
var Destructible = require('destructible')

// Evented semaphore.
var Signal = require('signal')

// Return the first not null-like value.
var coalesce = require('extant')

function Conduit (destructible, input, output, buffer, receiver) {
    this._input = new Staccato.Readable(input)

    this.destroyed = false

    this._output = new Staccato.Writable(output)

    this.receiver = receiver

    this._slices = []

    this._record = new Jacket

    this._closed = new Signal

    destructible.markDestroyed(this)

    destructible.destruct.wait(this._closed, 'unlatch')
    destructible.destruct.wait(this._input, 'destroy')

    var pump = this.receiver.outbox.pump(this, '_write', destructible.monitor('outbox'))

    destructible.destruct.wait(this, function () {
        this.receiver.inbox.push(null)
    })

    this._consume(buffer, destructible.monitor('pump'))
}

Conduit.prototype._consume = cadence(function (async, buffer) {
    async(function () {
        this._parse(coalesce(buffer, Buffer.alloc(0)), async())
    }, function () {
        this._read(async())
    }, function () {
        this._closed.wait(async())
    })
})

Conduit.prototype._buffer = cadence(function (async, buffer, start, end) {
    async(function () {
        var length = Math.min(buffer.length - start, this._chunk.length)
        var slice = buffer.slice(start, start + length)
        start += length
        this._chunk.length -= length
        // If chunk length is zero we have gathered up all of our chunks so
        // assemble them, but if not then save the slice for eventual assembly.

        //
        if (this._chunk.length === 0) {
            // If we've gathered slices, assemble them, otherwise make a copy of
            // the buffered slice (TODO what? but why? is it necessary?)
            if (this._slices.length == 0) {
                slice = Buffer.from(slice)
            } else {
                this._slices.push(slice)
                slice = Buffer.concat(this._slices)
                this._slices.length = 0
            }

            // The buffer body can be nested arbitrarily deep in envelopes.
            var envelope = this._chunk.body
            var e = envelope
            while (e.body != null) {
                e = e.body
            }
            e.body = slice

            // Reset to read next record.
            this._chunk = null
            this._record = new Jacket

            // Enqueue the parsed evelope.
            this.receiver.inbox.enqueue(envelope, async())
        } else {
            this._slices.push(Buffer.from(slice))
        }
    }, function () {
        return start
    })
})

Conduit.prototype._json = cadence(function (async, buffer, start, end) {
    start = this._record.parse(buffer, start, end)
    async(function () {
        if (this._record.object != null) {
            var envelope = this._record.object
            switch (envelope.method) {
            case 'envelope':
                this.receiver.inbox.enqueue(envelope.body, async())
                break
            case 'chunk':
                this._chunk = this._record.object
                break
            case 'trailer':
                console.log('trailered')
                // var socket = this._sockets[envelope.to]
                this.receiver.inbox.enqueue(null, async())
                // delete this._sockets[envelope.to]
                break
            }
            this._record = new Jacket
        }
    }, function () {
        return start
    })
})

Conduit.prototype._parse = cadence(function (async, buffer) {
    var parse = async(function (start) {
        if (start == buffer.length) {
            return [ parse.break ]
        }
        if (this._chunk != null) {
            this._buffer(buffer, start, buffer.length, async())
        } else {
            this._json(buffer, start, buffer.length, async())
        }
    })(0)
})

Conduit.prototype._read = cadence(function (async) {
    var read = async(function () {
        this._input.read(async())
    }, function (buffer) {
        if (buffer == null) {
            return [ read.break ]
        }
        this._parse(buffer, async())
    })()
})

Conduit.prototype._write = cadence(function (async, envelope) {
    async(function () {
        if (envelope == null) {
            async(function () {
                // TODO And then destroy the conduit.
                this._output.write(JSON.stringify({
                    module: 'conduit',
                    method: 'trailer',
                    body: null
                }) + '\n', async())
            }, function () {
                this._output.destroy()
                this._closed.unlatch()
            })
        } else {
            var e = envelope
            while (e.body != null && typeof e.body == 'object' && !Buffer.isBuffer(e.body)) {
                e = e.body
            }
            if (Buffer.isBuffer(e.body)) {
                var body = e.body
                e.body = null
                var packet = JSON.stringify({
                    module: 'conduit',
                    method: 'chunk',
                    length: body.length,
                    body: envelope
                }) + '\n'
                e.body = body
                this._output.write(packet, async())
                this._output.write(e.body, async())
            } else {
                this._output.write(JSON.stringify({
                    module: 'conduit',
                    method: 'envelope',
                    body: envelope
                }) + '\n', async())
            }
        }
    }, function () {
        return []
    })
})

module.exports = cadence(function (async, destructible, input, output, receiver, buffer) {
    return new Conduit(destructible, input, output, buffer, receiver)
})
<commit_msg>Notes on stream ownership.<commit_after>// Control-flow utilities.
var cadence = require('cadence')
var assert = require('assert')

// An evented message queue.
var Procession = require('procession')

// Evented stream reading and writing.
var Staccato = require('staccato')

// JSON for use in packets.
var Jacket = require('nascent.jacket')

// Orderly destruction of complicated objects.
var Destructible = require('destructible')

// Evented semaphore.
var Signal = require('signal')

// Return the first not null-like value.
var coalesce = require('extant')

// Once we get our hands on the `input` and `output` we own them, we're going to
// use `end` to indicate an end of stream. At the outset I'd hand a muddled
// imagining of external management of the stream, so that this interpretation
// of its contents was a "separate concern."

//
function Conduit (destructible, input, output, buffer, receiver) {
    this._input = new Staccato.Readable(input)

    this.destroyed = false

    this._output = new Staccato.Writable(output)

    this.receiver = receiver

    this._slices = []

    this._record = new Jacket

    this._closed = new Signal

    destructible.markDestroyed(this)

    destructible.destruct.wait(this._closed, 'unlatch')
    destructible.destruct.wait(this._input, 'destroy')

    var pump = this.receiver.outbox.pump(this, '_write', destructible.monitor('outbox'))

    destructible.destruct.wait(this, function () {
        this.receiver.inbox.push(null)
    })

    this._consume(buffer, destructible.monitor('pump'))
}

Conduit.prototype._consume = cadence(function (async, buffer) {
    async(function () {
        this._parse(coalesce(buffer, Buffer.alloc(0)), async())
    }, function () {
        this._read(async())
    }, function () {
        this._closed.wait(async())
    })
})

Conduit.prototype._buffer = cadence(function (async, buffer, start, end) {
    async(function () {
        var length = Math.min(buffer.length - start, this._chunk.length)
        var slice = buffer.slice(start, start + length)
        start += length
        this._chunk.length -= length
        // If chunk length is zero we have gathered up all of our chunks so
        // assemble them, but if not then save the slice for eventual assembly.

        //
        if (this._chunk.length === 0) {
            // If we've gathered slices, assemble them, otherwise make a copy of
            // the buffered slice (TODO what? but why? is it necessary?)
            if (this._slices.length == 0) {
                slice = Buffer.from(slice)
            } else {
                this._slices.push(slice)
                slice = Buffer.concat(this._slices)
                this._slices.length = 0
            }

            // The buffer body can be nested arbitrarily deep in envelopes.
            var envelope = this._chunk.body
            var e = envelope
            while (e.body != null) {
                e = e.body
            }
            e.body = slice

            // Reset to read next record.
            this._chunk = null
            this._record = new Jacket

            // Enqueue the parsed evelope.
            this.receiver.inbox.enqueue(envelope, async())
        } else {
            this._slices.push(Buffer.from(slice))
        }
    }, function () {
        return start
    })
})

Conduit.prototype._json = cadence(function (async, buffer, start, end) {
    start = this._record.parse(buffer, start, end)
    async(function () {
        if (this._record.object != null) {
            var envelope = this._record.object
            switch (envelope.method) {
            case 'envelope':
                this.receiver.inbox.enqueue(envelope.body, async())
                break
            case 'chunk':
                this._chunk = this._record.object
                break
            case 'trailer':
                console.log('trailered')
                // var socket = this._sockets[envelope.to]
                this.receiver.inbox.enqueue(null, async())
                // delete this._sockets[envelope.to]
                break
            }
            this._record = new Jacket
        }
    }, function () {
        return start
    })
})

Conduit.prototype._parse = cadence(function (async, buffer) {
    var parse = async(function (start) {
        if (start == buffer.length) {
            return [ parse.break ]
        }
        if (this._chunk != null) {
            this._buffer(buffer, start, buffer.length, async())
        } else {
            this._json(buffer, start, buffer.length, async())
        }
    })(0)
})

Conduit.prototype._read = cadence(function (async) {
    var read = async(function () {
        this._input.read(async())
    }, function (buffer) {
        if (buffer == null) {
            return [ read.break ]
        }
        this._parse(buffer, async())
    })()
})

Conduit.prototype._write = cadence(function (async, envelope) {
    async(function () {
        if (envelope == null) {
            async(function () {
                // TODO And then destroy the conduit.
                this._output.write(JSON.stringify({
                    module: 'conduit',
                    method: 'trailer',
                    body: null
                }) + '\n', async())
            }, function () {
                this._output.destroy()
                this._closed.unlatch()
            })
        } else {
            var e = envelope
            while (e.body != null && typeof e.body == 'object' && !Buffer.isBuffer(e.body)) {
                e = e.body
            }
            if (Buffer.isBuffer(e.body)) {
                var body = e.body
                e.body = null
                var packet = JSON.stringify({
                    module: 'conduit',
                    method: 'chunk',
                    length: body.length,
                    body: envelope
                }) + '\n'
                e.body = body
                this._output.write(packet, async())
                this._output.write(e.body, async())
            } else {
                this._output.write(JSON.stringify({
                    module: 'conduit',
                    method: 'envelope',
                    body: envelope
                }) + '\n', async())
            }
        }
    }, function () {
        return []
    })
})

module.exports = cadence(function (async, destructible, input, output, receiver, buffer) {
    return new Conduit(destructible, input, output, buffer, receiver)
})
<|endoftext|>


<commit_before>/**
 * Aifang Javascript Framework.
 * Copyright 2012 ANJUKE Inc. All rights reserved.
 *
 *
 * 这是cookie核心文件，
 *
 *
 * @path: cookie/cookie.js
 * @author: Jock
 * @version: 1.0.0
 * @date: 2012/02/10
 *
 */

(function(J){
    var D = document,
        millisecond = 24 * 60 * 60 * 1000,
        encode = encodeURIComponent,
        decode = decodeURIComponent;

    /**
     * 验证字符串是否合法的cookie值
     *
     * @param {String} val cookie值
     * @return {Boolean} 是否合法的cookie值
     */
    function validString(val){
        return J.isString(val) && '' !== val;
    }

    /**
     * 设置cookie
     *
     * @param {String} name cookie名称
     * @param {String} value cookie值
     * @param {String} date cookie过期时间
     * @param {String} path cookie path
     * @param {String} domain cookie domain
     * @param {String} secure cookie secure
     * @return null
     */
    /*function setCookie(name, value, date, domain, path, secure){
        D.cookie = name + "=" + String(encode( value )) +
                ((date) ? ";expires=" + date.toGMTString() : "") +
                (validString(path) ? ";path=" + path : "") +
                (validString(domain) ? ";domain=" + domain : "" ) +
                ((secure) ? ";secure" : "" );
    }*/

    function setCookie(name, value, expires, domain, path, secure){
        var today = new Date();
        today.setTime(today.getTime());
        if (expires) {
            expires = expires * 1000 * 60 * 60 * 24;
        }
        var expires_date = new Date(today.getTime() + (expires));
        document.cookie = name + "=" + escape(value) +
        ((expires) ? ";expires=" + expires_date.toGMTString() : "") +
        ((path) ? ";path=" + path: "") +
        ((domain) ? ";domain=" + domain: "") +
        ((secure) ? ";secure": "");
    }

    var cookie = {
        /**
         * 获取cookie值
         *
         * @param {String} name cookie名称
         * @return {String} cookie值
         */
        /*getCookie: function (check_name) {
            var a_all_cookies = document.cookie.split(';');
            var a_temp_cookie = '';
            var cookie_name = '';
            var cookie_value = '';
            var b_cookie_found = false;
            for (i = 0; i < a_all_cookies.length; i++) {
                a_temp_cookie = a_all_cookies[i].split('=');
                cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');
                if (cookie_name == check_name) {
                    b_cookie_found = true;
                    if (a_temp_cookie.length > 1) {
                        cookie_value = unescape(a_temp_cookie[1].replace(/^\s+|\s+$/g, ''));
                    }
                    return cookie_value;
                    break;
                }
                a_temp_cookie = null;
                cookie_name = '';
            }
            if (!b_cookie_found) {
                return null;
            }
        },*/
        getCookie: function (name) {
            var ret, m;
            if (validString(name)) {
                if ((m = String(D.cookie).match(
                    new RegExp('(?:^| )' + name + '(?:(?:=([^;]*))|;|$)')))) {
                    ret = m[1] ? decode(m[1]) : '';
                }
            }
            return ret;
        },
        /**
         * 设置cookie
         *
         * @param {String} name cookie名称
         * @param {String} value cookie值
         * @param {String} expires cookie过期时间 （单位天）
         * @param {String} path cookie path
         * @param {String} domain cookie domain
         * @param {String} secure cookie secure
         * @return null
         */
        setCookie: function(name, value, expires, domain, path, secure) {
            var date = '';
            if (expires) {
                date = new Date();
                date.setTime(date.getTime() + expires * millisecond);
            }
            setCookie(name, value, date, domain, path, secure)
        },
        /*setCookie: function(name, value, expires, domain, path, secure) {
            setCookie(name, value, expires, domain, path, secure)
        },*/
        /**
         * 删除cookie
         *
         * @param {String} name cookie名称
         * @return null
         */
        rmCookie: function(name, domain, path, secure){
            var expires = new Date();
            expires.setTime(expires.getTime() - 10000);
            setCookie(name, '', expires, domain, path, secure)
        }
    };

    J.add('cookie', cookie);

    J.mix(J, cookie);

})(J);

<commit_msg>fix cookie<commit_after>/**
 * Aifang Javascript Framework.
 * Copyright 2012 ANJUKE Inc. All rights reserved.
 *
 *
 * 这是cookie核心文件，
 *
 *
 * @path: cookie/cookie.js
 * @author: Jock
 * @version: 1.0.0
 * @date: 2012/02/10
 *
 */

(function(J){
    var D = document,
        millisecond = 24 * 60 * 60 * 1000,
        encode = encodeURIComponent,
        decode = decodeURIComponent;

    /**
     * 验证字符串是否合法的cookie值
     *
     * @param {String} val cookie值
     * @return {Boolean} 是否合法的cookie值
     */
    function validString(val){
        return J.isString(val) && '' !== val;
    }

    /**
     * 设置cookie
     *
     * @param {String} name cookie名称
     * @param {String} value cookie值
     * @param {String} date cookie过期时间
     * @param {String} path cookie path
     * @param {String} domain cookie domain
     * @param {String} secure cookie secure
     * @return null
     */
    function setCookie(name, value, date, domain, path, secure){
        D.cookie = name + "=" + String(encode( value )) +
                ((date) ? ";expires=" + date.toGMTString() : "") +
                (validString(path) ? ";path=" + path : "") +
                (validString(domain) ? ";domain=" + domain : "" ) +
                ((secure) ? ";secure" : "" );
    }

    /*function setCookie(name, value, expires, domain, path, secure){
        var today = new Date();
        today.setTime(today.getTime());
        if (expires) {
            expires = expires * 1000 * 60 * 60 * 24;
        }
        var expires_date = new Date(today.getTime() + (expires));
        document.cookie = name + "=" + escape(value) +
        ((expires) ? ";expires=" + expires_date.toGMTString() : "") +
        ((path) ? ";path=" + path: "") +
        ((domain) ? ";domain=" + domain: "") +
        ((secure) ? ";secure": "");
    }*/

    var cookie = {
        /**
         * 获取cookie值
         *
         * @param {String} name cookie名称
         * @return {String} cookie值
         */
        /*getCookie: function (check_name) {
            var a_all_cookies = document.cookie.split(';');
            var a_temp_cookie = '';
            var cookie_name = '';
            var cookie_value = '';
            var b_cookie_found = false;
            for (i = 0; i < a_all_cookies.length; i++) {
                a_temp_cookie = a_all_cookies[i].split('=');
                cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');
                if (cookie_name == check_name) {
                    b_cookie_found = true;
                    if (a_temp_cookie.length > 1) {
                        cookie_value = unescape(a_temp_cookie[1].replace(/^\s+|\s+$/g, ''));
                    }
                    return cookie_value;
                    break;
                }
                a_temp_cookie = null;
                cookie_name = '';
            }
            if (!b_cookie_found) {
                return null;
            }
        },*/
        getCookie: function (name) {
            var ret, m;
            if (validString(name)) {
                if ((m = String(D.cookie).match(
                    new RegExp('(?:^| )' + name + '(?:(?:=([^;]*))|;|$)')))) {
                    ret = m[1] ? decode(m[1]) : '';
                }
            }
            return ret;
        },
        /**
         * 设置cookie
         *
         * @param {String} name cookie名称
         * @param {String} value cookie值
         * @param {String} expires cookie过期时间 （单位天）
         * @param {String} path cookie path
         * @param {String} domain cookie domain
         * @param {String} secure cookie secure
         * @return null
         */
        setCookie: function(name, value, expires, domain, path, secure) {
            var date = '';
            if (expires) {
                date = new Date();
                date.setTime(date.getTime() + expires * millisecond);
            }
            setCookie(name, value, date, domain, path, secure)
        },
        /*setCookie: function(name, value, expires, domain, path, secure) {
            setCookie(name, value, expires, domain, path, secure)
        },*/
        /**
         * 删除cookie
         *
         * @param {String} name cookie名称
         * @return null
         */
        rmCookie: function(name, domain, path, secure){
            var expires = new Date();
            expires.setTime(expires.getTime() - 10000);
            setCookie(name, '', expires, domain, path, secure)
        }
    };

    J.add('cookie', cookie);

    J.mix(J, cookie);

})(J);

<|endoftext|>


<commit_before>import d3 from 'd3';
import {
    TextBox
} from 'd3plus-text';

export default function openmetadesign_viz(data) {

    // The container for the viz
    var d3Container = document.getElementById("d3-container");

    // Margins
    // https://bl.ocks.org/mbostock/3019563
    var margin = {
        top: 15,
        right: 10,
        bottom: 10,
        left: 10
    };
    var gutter = 10;
    var labelHeight = 20;

    // Get dimensions of the container on window resize
    window.addEventListener("resize", function(d) {
        width = d3Container.clientWidth;
        height = d3Container.clientHeight;
        console.log(width, height);
    });

    // Add the SVG to the container
    var svg = d3.select('#d3-container').append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Emojis for reactions
    // Source: https://github.com/twitter/twemoji
    // License: CC-BY

    var emojis = {
        simple_smile: "",
        heart_eyes: '',
        worried: "",
        angry: "",
        up: "",
        down: ""
    }

    emojis.worried = "";
    emojis.angry = "";
    emojis.up = "";
    emojis.down = "";

    // Functions for reusable elements

    // Load a svg file and append it to a parent element
    var loadSVG = function(url, parent, x, y, scale) {

        var loadedSVG = parent.append("g");

        d3.xml(url, function(xml) {
            var svgFile = document.importNode(xml.documentElement, true);
            loadedSVG.each(function(d, i) {
                this.appendChild(svgFile.cloneNode(true));
            });
        });

        loadedSVG.attr("transform", "translate(" + x + "," + y + ")");
        loadedSVG.attr("transform", "scale(" + scale + ")");

        return loadedSVG;
    }

    // Create an emoji button
    var addEmoji = function(x, y, radius, parent, type) {

        var emoji = parent.append("g");

        // Add the circle
        emoji.append("circle")
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", radius)
            .attr("class", "svg-button");

        // Load SVG
        loadSVG("/emojis/1f603.svg", emoji, x + 10, 0, 0.03);

        // Add classes
        emoji.attr("class", "svg-emoji")
            .on("mouseover", function() {
                d3.select(this)
                    .attr("filter", "url(#glow)");
            })
            .on("mouseout", function() {
                d3.select(this)
                    .attr("filter", null);
            });

        return emoji;

    }

    // Create a button with a FontAwesome icon
    var addButton = function(x, y, radius, parent, iconCode) {

        var button = parent.append("g");

        // Add the circle
        button.append("circle")
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", radius);

        // Add the icon
        button.append('text')
            .attr("x", x)
            .attr("y", y)
            .attr("text-anchor", "middle")
            .attr("dominant-baseline", "central")
            .style("font-family", "FontAwesome")
            .style("font-size", radius.toString() + "px")
            .text(iconCode);

        // Add classes
        button.attr("class", "svg-button")
            .on("mouseover", function() {
                d3.select(this)
                    .attr("filter", "url(#glow)");
            })
            .on("mouseout", function() {
                d3.select(this)
                    .attr("filter", null);
            });

        return button;

    }

    // Create an activity
    var addActivity = function(x, y, parent) {

        // Dimensions
        var participationContainerWidth = 30;
        var mainContainerWidth = 170;
        var containerHeight = 500;

        var activity = parent.append("g");

        // Add the participation container
        var participationContainer = activity.append("g").attr("class", "svg-activity-participation");

        participationContainer.append("rect")
            .attr("x", x)
            .attr("y", y)
            .attr("width", participationContainerWidth)
            .attr("height", containerHeight);

        var participationLevelX = x + participationContainerWidth / 2;
        var participationLevelY = y + 20;

        var participationLevel = participationContainer.append("text")
            .text("Participation Level %")
            .attr("x", 0)
            .attr("y", 0)
            .attr("class", "participation-level")
            .attr("transform", "translate(" + participationLevelX + "," + participationLevelY + ")");

        // Add the main container
        var mainContainer = activity.append("g").attr("class", "svg-activity");

        mainContainer.append("rect")
            .attr("x", x)
            .attr("y", y)
            .attr("width", mainContainerWidth)
            .attr("height", containerHeight);

        // Move the main container beside the participation container
        var mainContainerX = participationContainerWidth + 5;
        mainContainer.attr("transform", "translate(" + mainContainerX + ",0)");

        // Add the title with a D3Plus TextBox
        var activityTitle = mainContainer.append('g')
            .attr("id", "textbox-1");

        title = new TextBox()
            .data([{}])
            .select("#textbox-1")
            .text("Activity Title")
            .width(mainContainerWidth - 70)
            .x(x + 10)
            .y(y + 20)
            .fontSize(16)
            .fontWeight(500)
            .render();

        // Add the control buttons
        var activityButtons = mainContainer.append("g");
        // Discuss Button
        var discussButton = addButton(x + 5, y, 10, activityButtons, '\uf086');
        discussButton.attr("data-toggle", "modal")
            .classed("discuss-button", true);
        // Edit Button
        var editButton = addButton(x + 30, y, 10, activityButtons, '\uf044');
        editButton.attr("data-toggle", "modal")
            .classed("edit-button", true);
        // Flows Button
        var flowsButton = addButton(x + 55, y, 10, activityButtons, '\uf074');
        flowsButton.attr("data-toggle", "modal")
            .classed("flows-button", true);
        // Issues Button
        var issuesButton = addButton(x + 80, y, 10, activityButtons, '\uf071');
        issuesButton.attr("data-toggle", "modal")
            .classed("issues-button", true);
        // Delete Button
        var deleteButton = addButton(x + 105, y, 10, activityButtons, '\uf068');
        deleteButton.attr("data-toggle", "modal")
            .classed("delete-button", true);
        // Move the buttons below the title
        activityButtonY = 15 + // padding
            20 + // button size
            parseInt(activityTitle.node().getBBox().height); // title height
        activityButtons.attr("transform", "translate(15," + activityButtonY + ")");

        // Add the description with a D3Plus TextBox
        var activityDescription = mainContainer.append('g')
            .attr("id", "textbox-2");

        description = new TextBox()
            .data([{}])
            .text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc sed turpis at dolor porta malesuada. Mauris sollicitudin mi lorem, eu imperdiet risus fermentum vitae. Suspendisse in velit in felis semper vestibulum non eget nunc. Aliquam ultricies, mauris a rutrum aliquet, justo ante varius odio, at consequat tortor sapien porttitor nisl.")
            .width(mainContainerWidth - 30)
            .select("#textbox-2")
            .x(x + 10)
            .y(y + activityButtonY + 20)
            .fontSize(14)
            .fontWeight(400)
            .render();

        // Add emojis
        // var activityEmojis = mainContainer.append("g");
        // addEmoji(x, y, 10, activityEmojis, "smile");

        // Return the whole activity
        return activity;

    }

    // Filters
    var defs = svg.append("defs");
    // Glow filter
    var glow = defs.append("filter")
        .attr("id", "glow");
    glow.append("feGaussianBlur")
        .attr("stdDeviation", "1.5")
        .attr("result", "coloredBlur");
    var feMerge = glow.append("feMerge");
    feMerge.append("feMergeNode")
        .attr("in", "coloredBlur");
    feMerge.append("feMergeNode")
        .attr("in", "SourceGraphic");

    // Draw everything

    // Layout initialization
    var timeG = svg.append("g");
    var journeyG = svg.append("g");
    var blueprintG = svg.append("g");

    // Debug: see the border of each group
    svg.attr("style", "outline: thin solid black;");
    timeG.attr("style", "outline: thin solid green;");
    journeyG.attr("style", "outline: thin solid red;");
    blueprintG.attr("style", "outline: thin solid blue;");

    // Draw the Time section

    // Time scale and axis
    var yScale = d3.scaleTime()
        .domain([new Date(2000, 0, 1), new Date(2001, 0, 11)])
        .range([0, 800]);
    yAxis = d3.axisLeft().scale(yScale)
        .ticks(16)
        .tickSize(10);
    timeG.attr("id", "yAxisG")
        .call(yAxis);

    // Time label
    var timeLabel = timeG.append("text")
        .text("Time")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    // Draw the Journey section
    // Journey label
    var journeyLabel = journeyG.append("text")
        .text("Journey")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    journeyG.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", 50)
        .attr("height", 20)
        .attr("fill", "orange");

    // Draw the Blueprint section
    // Bluepring label
    var blueprintLabel = blueprintG.append("text")
        .text("Blueprint")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    // Create a sample activity
    var activity2 = addActivity(0, 0, blueprintG);

    // Layout: organize sections
    // In case we need to get the transform of an element: https://stackoverflow.com/a/38753017/2237113
    // Translate timeG according to the label width
    var timeGX = timeG.node().getBBox().width;
    timeG.attr("transform", "translate(" + timeGX + "," + labelHeight + ")");
    // Translate journeyG it after the timeG section
    var journeyGX = timeGX + timeG.node().getBBox().x + timeG.node().getBBox().width + gutter;
    journeyG.attr("transform", "translate(" + journeyGX + "," + labelHeight + ")");
    // Translate blueprintG after the journeyG section
    var blueprintGX = journeyGX + journeyG.node().getBBox().width + gutter;
    blueprintG.attr("transform", "translate(" + blueprintGX + "," + labelHeight + ")");

}
<commit_msg>Viz: subdivide the blueprint section into the 4 sections, move journey section after it<commit_after>import d3 from 'd3';
import {
    TextBox
} from 'd3plus-text';

export default function openmetadesign_viz(data) {

    // The container for the viz
    var d3Container = document.getElementById("d3-container");

    // Margins
    // https://bl.ocks.org/mbostock/3019563
    var margin = {
        top: 15,
        right: 10,
        bottom: 10,
        left: 10
    };
    var gutter = 10;
    var labelHeight = 20;

    // Get dimensions of the container on window resize
    window.addEventListener("resize", function(d) {
        width = d3Container.clientWidth;
        height = d3Container.clientHeight;
        console.log(width, height);
    });

    // Add the SVG to the container
    var svg = d3.select('#d3-container').append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Emojis for reactions
    // Source: https://github.com/twitter/twemoji
    // License: CC-BY

    var emojis = {
        simple_smile: "",
        heart_eyes: '',
        worried: "",
        angry: "",
        up: "",
        down: ""
    }

    emojis.worried = "";
    emojis.angry = "";
    emojis.up = "";
    emojis.down = "";

    // Functions for reusable elements

    // Load a svg file and append it to a parent element
    var loadSVG = function(url, parent, x, y, scale) {

        var loadedSVG = parent.append("g");

        d3.xml(url, function(xml) {
            var svgFile = document.importNode(xml.documentElement, true);
            loadedSVG.each(function(d, i) {
                this.appendChild(svgFile.cloneNode(true));
            });
        });

        loadedSVG.attr("transform", "translate(" + x + "," + y + ")");
        loadedSVG.attr("transform", "scale(" + scale + ")");

        return loadedSVG;
    }

    // Create an emoji button
    var addEmoji = function(x, y, radius, parent, type) {

        var emoji = parent.append("g");

        // Add the circle
        emoji.append("circle")
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", radius)
            .attr("class", "svg-button");

        // Load SVG
        loadSVG("/emojis/1f603.svg", emoji, x + 10, 0, 0.03);

        // Add classes
        emoji.attr("class", "svg-emoji")
            .on("mouseover", function() {
                d3.select(this)
                    .attr("filter", "url(#glow)");
            })
            .on("mouseout", function() {
                d3.select(this)
                    .attr("filter", null);
            });

        return emoji;

    }

    // Create a button with a FontAwesome icon
    var addButton = function(x, y, radius, parent, iconCode) {

        var button = parent.append("g");

        // Add the circle
        button.append("circle")
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", radius);

        // Add the icon
        button.append('text')
            .attr("x", x)
            .attr("y", y)
            .attr("text-anchor", "middle")
            .attr("dominant-baseline", "central")
            .style("font-family", "FontAwesome")
            .style("font-size", radius.toString() + "px")
            .text(iconCode);

        // Add classes
        button.attr("class", "svg-button")
            .on("mouseover", function() {
                d3.select(this)
                    .attr("filter", "url(#glow)");
            })
            .on("mouseout", function() {
                d3.select(this)
                    .attr("filter", null);
            });

        return button;

    }

    // Create an activity
    var addActivity = function(x, y, parent) {

        // Dimensions
        var participationContainerWidth = 30;
        var mainContainerWidth = 170;
        var containerHeight = 500;

        var activity = parent.append("g");

        // Add the participation container
        var participationContainer = activity.append("g").attr("class", "svg-activity-participation");

        participationContainer.append("rect")
            .attr("x", x)
            .attr("y", y)
            .attr("width", participationContainerWidth)
            .attr("height", containerHeight);

        var participationLevelX = x + participationContainerWidth / 2;
        var participationLevelY = y + 20;

        var participationLevel = participationContainer.append("text")
            .text("Participation Level %")
            .attr("x", 0)
            .attr("y", 0)
            .attr("class", "participation-level")
            .attr("transform", "translate(" + participationLevelX + "," + participationLevelY + ")");

        // Add the main container
        var mainContainer = activity.append("g").attr("class", "svg-activity");

        mainContainer.append("rect")
            .attr("x", x)
            .attr("y", y)
            .attr("width", mainContainerWidth)
            .attr("height", containerHeight);

        // Move the main container beside the participation container
        var mainContainerX = participationContainerWidth + 5;
        mainContainer.attr("transform", "translate(" + mainContainerX + ",0)");

        // Add the title with a D3Plus TextBox
        var activityTitle = mainContainer.append('g')
            .attr("id", "textbox-1");

        title = new TextBox()
            .data([{}])
            .select("#textbox-1")
            .text("Activity Title")
            .width(mainContainerWidth - 70)
            .x(x + 10)
            .y(y + 20)
            .fontSize(16)
            .fontWeight(500)
            .render();

        // Add the control buttons
        var activityButtons = mainContainer.append("g");
        // Discuss Button
        var discussButton = addButton(x + 5, y, 10, activityButtons, '\uf086');
        discussButton.attr("data-toggle", "modal")
            .classed("discuss-button", true);
        // Edit Button
        var editButton = addButton(x + 30, y, 10, activityButtons, '\uf044');
        editButton.attr("data-toggle", "modal")
            .classed("edit-button", true);
        // Flows Button
        var flowsButton = addButton(x + 55, y, 10, activityButtons, '\uf074');
        flowsButton.attr("data-toggle", "modal")
            .classed("flows-button", true);
        // Issues Button
        var issuesButton = addButton(x + 80, y, 10, activityButtons, '\uf071');
        issuesButton.attr("data-toggle", "modal")
            .classed("issues-button", true);
        // Delete Button
        var deleteButton = addButton(x + 105, y, 10, activityButtons, '\uf068');
        deleteButton.attr("data-toggle", "modal")
            .classed("delete-button", true);
        // Move the buttons below the title
        activityButtonY = 15 + // padding
            20 + // button size
            parseInt(activityTitle.node().getBBox().height); // title height
        activityButtons.attr("transform", "translate(15," + activityButtonY + ")");

        // Add the description with a D3Plus TextBox
        var activityDescription = mainContainer.append('g')
            .attr("id", "textbox-2");

        description = new TextBox()
            .data([{}])
            .text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc sed turpis at dolor porta malesuada. Mauris sollicitudin mi lorem, eu imperdiet risus fermentum vitae. Suspendisse in velit in felis semper vestibulum non eget nunc. Aliquam ultricies, mauris a rutrum aliquet, justo ante varius odio, at consequat tortor sapien porttitor nisl.")
            .width(mainContainerWidth - 30)
            .select("#textbox-2")
            .x(x + 10)
            .y(y + activityButtonY + 20)
            .fontSize(14)
            .fontWeight(400)
            .render();

        // Add emojis
        // var activityEmojis = mainContainer.append("g");
        // addEmoji(x, y, 10, activityEmojis, "smile");

        // Return the whole activity
        return activity;

    }

    // Filters
    var defs = svg.append("defs");
    // Glow filter
    var glow = defs.append("filter")
        .attr("id", "glow");
    glow.append("feGaussianBlur")
        .attr("stdDeviation", "1.5")
        .attr("result", "coloredBlur");
    var feMerge = glow.append("feMerge");
    feMerge.append("feMergeNode")
        .attr("in", "coloredBlur");
    feMerge.append("feMergeNode")
        .attr("in", "SourceGraphic");

    // Draw everything

    // Layout initialization
    var timeG = svg.append("g");
    var blueprintCustomerG = svg.append("g");
    var blueprintFrontG = svg.append("g");
    var blueprintBackG = svg.append("g");
    var blueprintSupportG = svg.append("g");
    var journeyG = svg.append("g");

    // Debug: see the border of each group
    svg.attr("style", "outline: thin solid black;");
    timeG.attr("style", "outline: thin solid green;");
    journeyG.attr("style", "outline: thin solid red;");

    // Draw the Time section

    // Time scale and axis
    var yScale = d3.scaleTime()
        .domain([new Date(2000, 0, 1), new Date(2001, 0, 11)])
        .range([0, 800]);
    yAxis = d3.axisLeft().scale(yScale)
        .ticks(16)
        .tickSize(10);
    timeG.attr("id", "yAxisG")
        .call(yAxis);

    // Time label
    var timeLabel = timeG.append("text")
        .text("Time")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    // Draw the Blueprint section
    // Customer section
    var blueprintCustomerLabel = blueprintCustomerG.append("text")
        .text("Customer")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    // Create a sample activity
    var activity2 = addActivity(0, 0, blueprintCustomerG);

    // Front-Office section
    var blueprintFrontLabel = blueprintFrontG.append("text")
        .text("Front-Office")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    // Create a sample activity
    var activity3 = addActivity(0, 0, blueprintFrontG);

    // Back-Office section
    var blueprintBackLabel = blueprintBackG.append("text")
        .text("Back-Office")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    // Create a sample activity
    var activity4 = addActivity(0, 0, blueprintBackG);

    // Support section
    var blueprintSupportLabel = blueprintSupportG.append("text")
        .text("Support Processes")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    // Create a sample activity
    var activity5 = addActivity(0, 0, blueprintSupportG);

    // Draw the Journey section
    // Journey label
    var journeyLabel = journeyG.append("text")
        .text("Journey")
        .attr("x", 0)
        .attr("y", 0)
        .attr("class", "svg-label")
        .attr("transform", "translate(0,-" + labelHeight + ")");

    journeyG.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", 50)
        .attr("height", 20)
        .attr("fill", "orange");

    // Layout: organize sections
    // In case we need to get the transform of an element: https://stackoverflow.com/a/38753017/2237113
    // Translate timeG according to the label width
    var timeGX = timeG.node().getBBox().width;
    timeG.attr("transform", "translate(" + timeGX + "," + labelHeight + ")");
    // Translate blueprintCustomerG after the journeyG section
    var blueprintGX = timeGX + timeG.node().getBBox().x + timeG.node().getBBox().width + gutter;
    blueprintCustomerG.attr("transform", "translate(" + blueprintGX + "," + labelHeight + ")");
    // Translate blueprintFrontG after the journeyG section
    blueprintGX = blueprintGX + blueprintCustomerG.node().getBBox().width + gutter;
    blueprintFrontG.attr("transform", "translate(" + blueprintGX + "," + labelHeight + ")");
    // Translate blueprintBackG after the journeyG section
    blueprintGX = blueprintGX + blueprintFrontG.node().getBBox().width + gutter;
    blueprintBackG.attr("transform", "translate(" + blueprintGX + "," + labelHeight + ")");
    // Translate blueprintSupportG after the journeyG section
    blueprintGX = blueprintGX + blueprintBackG.node().getBBox().width + gutter;
    blueprintSupportG.attr("transform", "translate(" + blueprintGX + "," + labelHeight + ")");
    // Translate journeyG it after the timeG section
    var journeyGX = blueprintGX + blueprintSupportG.node().getBBox().width + gutter;
    journeyG.attr("transform", "translate(" + journeyGX + "," + labelHeight + ")");

}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
// MATERIAL
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';

class EditKeymapModal extends Component {

  constructor() {
    super();
    this.state = {
      open: true,
      keymapName: '',
      keymapCommand: '',
      keyID: '',
      waitingInput: false,
    };
    this.handleChangeName     = this.handleChangeName.bind(this);
    this.handleChangeCommand  = this.handleChangeCommand.bind(this);
    this.handleSubmit         = this.handleSubmit.bind(this);
    this.handleBindMidiKey    = this.handleBindMidiKey.bind(this);
    this.handleError          = this.handleError.bind(this);
  }

  componentWillMount() {
    if (this.props.action === 'edit') {
      this.setState({
        keymapName: this.props.keymap.name,
        keymapCommand: this.props.keymap.command,
        keyID: this.props.keymap.keyID,
      });
    }
  }

  componentDidMount() {
    this.props.stopExecuteBindings();
  }

  componentWillUnmount() {
    this.props.startExecuteBindings();
  }

  componentWillReceiveProps(nextProps) {
    if (this.state.waitingInput &&
      nextProps.events[nextProps.events.length-1].action === 'keyUp') {

      console.log(nextProps.events[nextProps.events.length-1].key);

      const redundancyKeymaps = this.props.keymaps.filter((keymap) => {
        return(keymap.keyID === nextProps.events[nextProps.events.length-1].key);
      }).length;

      if (redundancyKeymaps === 0) {
        this.setState({
          waitingInput: false,
          keyID: nextProps.events[nextProps.events.length-1].key
        });
        this.props.stopListeningEvents();
      }
      else {
        this.handleError('This key is already used.')
      }
    }
  }

  handleBindMidiKey() {
    this.props.startListeningEvents();
    this.setState({
      waitingInput: true,
      keyID: ''
    });
  }

  handleChangeName(event) {
    this.setState({
      keymapName: event.target.value,
    });
  };

  handleChangeCommand(event) {
    this.setState({
      keymapCommand: event.target.value,
    });
  };

  handleError(error) {
    console.log(error);
  }

  handleSubmit() {
    const newKeymap = {
      name: this.state.keymapName,
      command: this.state.keymapCommand,
      keyID: this.state.keyID
    };

    if (this.props.action === 'edit' && this.props.index) {
      this.props.editKeymap(this.props.index, newKeymap);
    }
    if (this.props.action === 'add') {
      this.props.addKeymap(newKeymap);
    }

    this.props.closeDialog();
  };

  render() {

    // If all the form are filled => true
    const formFilled = !(
      this.state.keymapName.length > 2 &&
      this.state.keymapCommand.length > 2 &&
      this.state.keyID !== null
    );

    const actions = [
      <FlatButton
        label="Cancel"
        primary={true}
        onTouchTap={this.props.closeDialog}
      />,
      <FlatButton
        label="Add"
        primary={true}
        disabled={formFilled}
        onTouchTap={this.handleSubmit}
      />,
    ];

    const labelBindingKeyButton = () => {
      if (this.state.keyID === '' && !this.state.waitingInput) {
        return 'Bind MIDI Key';
      }
      if (this.state.keyID !== '' && !this.state.waitingInput) {
        return 'Key : '+ this.state.keyID;
      }
      if (this.state.waitingInput) {
        return 'Press the key to assign...';
      }
    };

    let title;
    if (this.props.action === 'add') title = 'Add a new keymap';
    if (this.props.action === 'edit') title = 'Edit keymap';

    return (

        <Dialog
          title={title}
          actions={actions}
          modal={true}
          open={this.state.open}
        >
          <TextField
            floatingLabelText="Name"
            value={this.state.keymapName}
            onChange={this.handleChangeName}
            style={{width: '100%'}}
          />
          <TextField
            floatingLabelText="Command"
            value={this.state.keymapCommand}
            onChange={this.handleChangeCommand}
            style={{width: '100%'}}
          />
          <RaisedButton
            label={labelBindingKeyButton()}
            labelPosition="before"
            containerElement="label"
            onTouchTap={this.handleBindMidiKey}
            disableTouchRipple
            style={{
              marginTop: '1em'
            }}
          />
        </Dialog>

    );
  }
}
export default EditKeymapModal;
<commit_msg>refactor(editKeymap): Reducing if<commit_after>import React, { Component } from 'react';
// MATERIAL
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';

class EditKeymapModal extends Component {

  constructor() {
    super();
    this.state = {
      open: true,
      keymapName: '',
      keymapCommand: '',
      keyID: '',
      waitingInput: false,
    };
    this.handleChangeName     = this.handleChangeName.bind(this);
    this.handleChangeCommand  = this.handleChangeCommand.bind(this);
    this.handleSubmit         = this.handleSubmit.bind(this);
    this.handleBindMidiKey    = this.handleBindMidiKey.bind(this);
    this.handleError          = this.handleError.bind(this);
  }

  componentWillMount() {
    if (this.props.action === 'edit') {
      this.setState({
        keymapName: this.props.keymap.name,
        keymapCommand: this.props.keymap.command,
        keyID: this.props.keymap.keyID,
      });
    }
  }

  componentDidMount() {
    this.props.stopExecuteBindings();
  }

  componentWillUnmount() {
    this.props.startExecuteBindings();
  }

  componentWillReceiveProps(nextProps) {
    this.handleMidiEvent(nextProps);
  }

  handleMidiEvent(nextProps) {
    if (this.state.waitingInput &&
      nextProps.events[nextProps.events.length-1].action === 'keyUp') {

      const redundancyKeymaps = this.props.keymaps.filter((keymap) => {
        return(keymap.keyID === nextProps.events[nextProps.events.length-1].key);
      }).length;

      if (redundancyKeymaps === 0) {
        this.setState({
          waitingInput: false,
          keyID: nextProps.events[nextProps.events.length-1].key
        });
        this.props.stopListeningEvents();
      }
      else {
        this.handleError('This key is already used.')
      }
    }
  }

  handleBindMidiKey() {
    this.props.startListeningEvents();
    this.setState({
      waitingInput: true,
      keyID: ''
    });
  }

  handleChangeName(event) {
    this.setState({
      keymapName: event.target.value,
    });
  };

  handleChangeCommand(event) {
    this.setState({
      keymapCommand: event.target.value,
    });
  };

  handleError(error) {
    console.log(error);
  }

  handleSubmit() {
    const newKeymap = {
      name: this.state.keymapName,
      command: this.state.keymapCommand,
      keyID: this.state.keyID
    };

    if (this.props.action === 'edit' && this.props.index) {
      this.props.editKeymap(this.props.index, newKeymap);
    }
    if (this.props.action === 'add') {
      this.props.addKeymap(newKeymap);
    }

    this.props.closeDialog();
  };

  render() {

    // If all the form are filled => true
    const formFilled = !(
      this.state.keymapName.length > 2 &&
      this.state.keymapCommand.length > 2 &&
      this.state.keyID !== null
    );

    const actions = [
      <FlatButton
        label="Cancel"
        primary={true}
        onTouchTap={this.props.closeDialog}
      />,
      <FlatButton
        label="Add"
        primary={true}
        disabled={formFilled}
        onTouchTap={this.handleSubmit}
      />,
    ];

    const labelBindingKeyButton = () => {
      if (this.state.keyID === '' && !this.state.waitingInput) return 'Bind MIDI Key';
      if (this.state.keyID !== '' && !this.state.waitingInput) return 'Key : '+ this.state.keyID;
      if (this.state.waitingInput) return 'Press the key to assign...';

    };

    let title;
    if (this.props.action === 'add') title = 'Add a new keymap';
    if (this.props.action === 'edit') title = 'Edit keymap';

    return (

        <Dialog
          title={title}
          actions={actions}
          modal={true}
          open={this.state.open}
        >
          <TextField
            floatingLabelText="Name"
            value={this.state.keymapName}
            onChange={this.handleChangeName}
            style={{width: '100%'}}
          />
          <TextField
            floatingLabelText="Command"
            value={this.state.keymapCommand}
            onChange={this.handleChangeCommand}
            style={{width: '100%'}}
          />
          <RaisedButton
            label={labelBindingKeyButton()}
            labelPosition="before"
            containerElement="label"
            onTouchTap={this.handleBindMidiKey}
            disableTouchRipple
            style={{
              marginTop: '1em'
            }}
          />
        </Dialog>

    );
  }
}
export default EditKeymapModal;
<|endoftext|>