<commit_before>import { isNone } from '@ember/utils';
import config from 'ember-get-config';
/* global L */

export function initialize(/* container, application */) {
  if (typeof FastBoot === 'undefined') {
    let prefix = '';

    if (!isNone(config.rootURL)) {
      prefix = config.rootURL;
    } else if (!isNone(config.baseURL)) {
      prefix = config.baseURL;
    }

    L.Icon.Default.imagePath = `${prefix}assets/images/`;
  }
}

export default {
  name: 'leaflet-assets',
  initialize
};
<commit_msg>check in L is Present in initializer<commit_after>import { isNone } from '@ember/utils';
import config from 'ember-get-config';
/* global L */

export function initialize(/* container, application */) {
  if (typeof FastBoot === 'undefined' && typeof L !== 'undefined') {
    let prefix = '';

    if (!isNone(config.rootURL)) {
      prefix = config.rootURL;
    } else if (!isNone(config.baseURL)) {
      prefix = config.baseURL;
    }

    L.Icon.Default.imagePath = `${prefix}assets/images/`;
  }
}

export default {
  name: 'leaflet-assets',
  initialize
};
<|endoftext|>


<commit_before>// old_results: array of documents.
// new_results: array of documents.
// observer: object with 'added', 'changed', 'moved',
//           'removed' functions (each optional)
// deepcopy: if true, elements of new_results that are passed to callbacks are
//          deepcopied first
LocalCollection._diffQuery = function (old_results, new_results, observer, deepcopy) {

  var new_presence_of_id = {};
  _.each(new_results, function (doc) {
    new_presence_of_id[doc._id] = true;
  });

  var old_index_of_id = {};
  _.each(old_results, function (doc, i) {
    old_index_of_id[doc._id] = i;
  });

  // "maybe deepcopy"
  var mdc = (deepcopy ? LocalCollection._deepcopy : _.identity);

  // ALGORITHM:
  //
  // We walk old_idx through the old_results array and
  // new_idx through the new_results array at the same time.
  // These pointers establish a sort of correspondence between
  // old docs and new docs (identified by their _ids).
  // If they point to the same doc (i.e. old and new docs
  // with the same _id), we can increment both pointers
  // and fire no 'moved' callbacks.  Otherwise, we must
  // increment one or the other and fire approprate 'added',
  // 'removed', and 'moved' callbacks.
  //
  // The process is driven by new_results, in that we try
  // make the observer's array look like new_results by
  // establishing each new doc in order.  The doc pointed
  // to by new_idx is the one we are trying to establish
  // at any given time.  If it doesn't exist in old_results,
  // we fire an 'added' callback.  If it does, we have a
  // choice of two ways to handle the situation.  We can
  // advance old_idx forward to the corresponding old doc,
  // treating all intervening old docs as moved or removed,
  // and the current doc as unmoved.  Or, we can simply
  // establish the new doc as next by moving it into place,
  // i.e. firing a single 'moved' callback to move the
  // doc from wherever it was before.  Generating a sequence
  // of 'moved' callbacks that is not just correct but small
  // (or minimal) is a matter of choosing which elements
  // to consider moved and which ones merely change position
  // by virtue of the movement of other docs.
  //
  // Calling callbacks with correct indices requires understanding
  // what the observer's array looks like at each iteration.
  // The observer's array is a concatenation of:
  // - new_results up to (but not including) new_idx, with the
  //   addition of some "bumped" docs that we are later going
  //   to move into place
  // - old_results starting at old_idx, minus any docs that we
  //   have already moved ("taken" docs)
  //
  // To keep track of "bumped" items -- docs in the observer's
  // array that we have skipped over, but will be moved forward
  // later when we get to their new position -- we keep a
  // "bump list" of indices into new_results where bumped items
  // occur.  [The idea is that by adding an item to the list (bumping
  // it), we can consider it dealt with, even though it is still there.]
  // The corresponding position of new_idx in the observer's array,
  // then, is new_idx + bump_list.length, and the position of
  // the nth bumped item in the observer's array is
  // bump_list[n] + n (to account for the previous bumped items
  // that are still there).
  //
  // A "taken" list is used in a sort of analogous way to track
  // the indices of the documents after old_idx in old_results
  // that we have moved, so that, conversely, even though we will
  // come across them in old_results, they are actually no longer
  // in the observer's array.
  //
  // To determine which docs should be considered "moved" (and which
  // merely change position because of other docs moving) we run
  // a "longest common subsequence" (LCS) algorithm.  The LCS of the
  // doc IDs and the new doc IDs gives the docs that should NOT be
  // considered moved.
  //
  // Overall, this diff implementation is asymptotically good, but could
  // be optimized to streamline execution and use less memory (e.g. not
  // have to build data structures with an entry for every doc).

  //////// LCS (longest common sequence, with respect to _id)
  // (see Wikipedia article on Longest Increasing Subsequence,
  // where the LIS is taken of the sequence of old indices of the
  // docs in new_results)
  //
  // unmoved_set: the output of the algorithm; members of the LCS,
  // in the form of indices into new_results
  var unmoved_set = {};
  // max_seq_len: length of LCS found so far
  var max_seq_len = 0;
  // seq_ends[i]: the index into new_results of the last doc in a
  // common subsequence of length of i+1 <= max_seq_len
  var N = new_results.length;
  var seq_ends = new Array(N);
  // ptrs:  the common subsequence ending with new_results[n] extends
  // a common subsequence ending with new_results[ptr[n]], unless
  // ptr[n] is -1.
  var ptrs = new Array(N);
  // virtual sequence of old indices of new results
  var old_idx_seq = function(i_new) {
    return old_index_of_id[new_results[i_new]._id];
  };
  // for each item in new_results, use it to extend a common subsequence
  // of length j <= max_seq_len
  for(var i=0; i<N; i++) {
    if (old_index_of_id[new_results[i]._id] !== undefined) {
      var j = max_seq_len;
      // this inner loop would traditionally be a binary search,
      // but scanning backwards we will likely find a subseq to extend
      // pretty soon, bounded for example by the total number of ops.
      // If this were to be changed to a binary search, we'd still want
      // to scan backwards a bit as an optimization.
      while (j > 0) {
        if (old_idx_seq(seq_ends[j-1]) < old_idx_seq(i))
          break;
        j--;
      }

      ptrs[i] = (j === 0 ? -1 : seq_ends[j-1]);
      seq_ends[j] = i;
      if (j+1 > max_seq_len)
        max_seq_len = j+1;
    }
  }

  // pull out the LCS/LIS into unmoved_set
  var idx = (max_seq_len === 0 ? -1 : seq_ends[max_seq_len-1]);
  while (idx >= 0) {
    unmoved_set[idx] = true;
    idx = ptrs[idx];
  }

  //////// Main Algorithm

  var old_idx = 0;
  var new_idx = 0;
  var bump_list = [];
  var bump_list_old_idx = [];
  var taken_list = [];

  var scan_to = function(old_j) {
    // old_j <= old_results.length (may scan to end)
    while (old_idx < old_j) {
      var old_doc = old_results[old_idx];
      var is_in_new = new_presence_of_id[old_doc._id];
      if (! is_in_new) {
        observer.removed && observer.removed(old_doc, new_idx + bump_list.length);
      } else {
        if (taken_list.length >= 1 && taken_list[0] === old_idx) {
          // already moved
          taken_list.shift();
        } else {
          // bump!
          bump_list.push(new_idx);
          bump_list_old_idx.push(old_idx);
        }
      }
      old_idx++;
    }
  };

  while (new_idx <= new_results.length) {
    if (new_idx < new_results.length) {
      var new_doc = new_results[new_idx];
      var old_doc_idx = old_index_of_id[new_doc._id];
      if (old_doc_idx === undefined) {
        // insert
        observer.added && observer.added(mdc(new_doc), new_idx + bump_list.length);
      } else {
        var old_doc = old_results[old_doc_idx];
        //var is_unmoved = (old_doc_idx > old_idx); // greedy; not minimal
        var is_unmoved = unmoved_set[new_idx];
        if (is_unmoved) {
          if (old_doc_idx < old_idx)
            Meteor._debug("Assertion failed while diffing: nonmonotonic lcs data");
          // no move
          scan_to(old_doc_idx);
          if (! _.isEqual(old_doc, new_doc)) {
            observer.changed && observer.changed(
              mdc(new_doc), new_idx + bump_list.length, old_doc);
          }
          old_idx++;
        } else {
          // move into place
          var to_idx = new_idx + bump_list.length;
          var from_idx;
          if (old_doc_idx >= old_idx) {
            // move backwards
            from_idx = to_idx + old_doc_idx - old_idx;
            // must take number of "taken" items into account; also use
            // results of this binary search to insert new taken_list entry
            var num_taken_before = _.sortedIndex(taken_list, old_doc_idx);
            from_idx -= num_taken_before;
            taken_list.splice(num_taken_before, 0, old_doc_idx);
          } else {
            // move forwards, from bump list
            // (binary search applies)
            var b = _.indexOf(bump_list_old_idx, old_doc_idx, true);
            if (b < 0)
              Meteor._debug("Assertion failed while diffing: no bumped item");
            from_idx = bump_list[b] + b;
            to_idx--;
            bump_list.splice(b, 1);
            bump_list_old_idx.splice(b, 1);
          }
          if (from_idx != to_idx)
            observer.moved && observer.moved(mdc(old_doc), from_idx, to_idx);
          if (! _.isEqual(old_doc, new_doc)) {
            observer.changed && observer.changed(mdc(new_doc), to_idx, old_doc);
          }
        }
      }
    } else {
      scan_to(old_results.length);
    }
    new_idx++;
  }
  if (bump_list.length > 0) {
    Meteor._debug(old_results);
    Meteor._debug(new_results);
    Meteor._debug("Assertion failed while diffing: leftover bump_list "+
                  bump_list);
  }

};
<commit_msg>snapshots changes with Geoff (comments)<commit_after>// old_results: array of documents.
// new_results: array of documents.
// observer: object with 'added', 'changed', 'moved',
//           'removed' functions (each optional)
// deepcopy: if true, elements of new_results that are passed to callbacks are
//          deepcopied first
LocalCollection._diffQuery = function (old_results, new_results, observer, deepcopy) {

  var new_presence_of_id = {};
  _.each(new_results, function (doc) {
    new_presence_of_id[doc._id] = true;
  });

  var old_index_of_id = {};
  _.each(old_results, function (doc, i) {
    old_index_of_id[doc._id] = i;
  });

  // "maybe deepcopy"
  var mdc = (deepcopy ? LocalCollection._deepcopy : _.identity);

  // ALGORITHM:
  //
  // We walk old_idx through the old_results array and
  // new_idx through the new_results array at the same time.
  // These pointers establish a sort of correspondence between
  // old docs and new docs (identified by their _ids).
  // If they point to the same doc (i.e. old and new docs
  // with the same _id), we can increment both pointers
  // and fire no 'moved' callbacks.  Otherwise, we must
  // increment one or the other and fire approprate 'added',
  // 'removed', and 'moved' callbacks.
  //
  // The process is driven by new_results, in that we try
  // make the observer's array look like new_results by
  // establishing each new doc in order.  The doc pointed
  // to by new_idx is the one we are trying to establish
  // at any given time.  If it doesn't exist in old_results,
  // we fire an 'added' callback.  If it does, we have a
  // choice of two ways to handle the situation.  We can
  // advance old_idx forward to the corresponding old doc,
  // treating all intervening old docs as moved or removed,
  // and the current doc as unmoved.  Or, we can simply
  // establish the new doc as next by moving it into place,
  // i.e. firing a single 'moved' callback to move the
  // doc from wherever it was before.  Generating a sequence
  // of 'moved' callbacks that is not just correct but small
  // (or minimal) is a matter of choosing which elements
  // to consider moved and which ones merely change position
  // by virtue of the movement of other docs.
  //
  // Calling callbacks with correct indices requires understanding
  // what the observer's array looks like at each iteration.
  // The observer's array is a concatenation of:
  // - new_results up to (but not including) new_idx, with the
  //   addition of some "bumped" docs that we are later going
  //   to move into place
  // - old_results starting at old_idx, minus any docs that we
  //   have already moved ("taken" docs)
  //
  // To keep track of "bumped" items -- docs in the observer's
  // array that we have skipped over, but will be moved forward
  // later when we get to their new position -- we keep a
  // "bump list" of indices into new_results where bumped items
  // occur.  [The idea is that by adding an item to the list (bumping
  // it), we can consider it dealt with, even though it is still there.]
  // The corresponding position of new_idx in the observer's array,
  // then, is new_idx + bump_list.length, and the position of
  // the nth bumped item in the observer's array is
  // bump_list[n] + n (to account for the previous bumped items
  // that are still there).
  //
  // A "taken" list is used in a sort of analogous way to track
  // the indices of the documents after old_idx in old_results
  // that we have moved, so that, conversely, even though we will
  // come across them in old_results, they are actually no longer
  // in the observer's array.
  //
  // To determine which docs should be considered "moved" (and which
  // merely change position because of other docs moving) we run
  // a "longest common subsequence" (LCS) algorithm.  The LCS of the
  // old doc IDs and the new doc IDs gives the docs that should NOT be
  // considered moved.
  //
  // Overall, this diff implementation is asymptotically good, but could
  // be optimized to streamline execution and use less memory (e.g. not
  // have to build data structures with an entry for every doc).

  // Asymptotically: O(N k) where k is number of ops, or potentially
  // O(N log N) if inner loop of LCS were made to be binary search.

  //////// LCS (longest common sequence, with respect to _id)
  // (see Wikipedia article on Longest Increasing Subsequence,
  // where the LIS is taken of the sequence of old indices of the
  // docs in new_results)
  //
  // unmoved_set: the output of the algorithm; members of the LCS,
  // in the form of indices into new_results
  var unmoved_set = {};
  // max_seq_len: length of LCS found so far
  var max_seq_len = 0;
  // seq_ends[i]: the index into new_results of the last doc in a
  // common subsequence of length of i+1 <= max_seq_len
  var N = new_results.length;
  var seq_ends = new Array(N);
  // ptrs:  the common subsequence ending with new_results[n] extends
  // a common subsequence ending with new_results[ptr[n]], unless
  // ptr[n] is -1.
  var ptrs = new Array(N);
  // virtual sequence of old indices of new results
  var old_idx_seq = function(i_new) {
    return old_index_of_id[new_results[i_new]._id];
  };
  // for each item in new_results, use it to extend a common subsequence
  // of length j <= max_seq_len
  for(var i=0; i<N; i++) {
    if (old_index_of_id[new_results[i]._id] !== undefined) {
      var j = max_seq_len;
      // this inner loop would traditionally be a binary search,
      // but scanning backwards we will likely find a subseq to extend
      // pretty soon, bounded for example by the total number of ops.
      // If this were to be changed to a binary search, we'd still want
      // to scan backwards a bit as an optimization.
      while (j > 0) {
        if (old_idx_seq(seq_ends[j-1]) < old_idx_seq(i))
          break;
        j--;
      }

      ptrs[i] = (j === 0 ? -1 : seq_ends[j-1]);
      seq_ends[j] = i;
      if (j+1 > max_seq_len)
        max_seq_len = j+1;
    }
  }

  // pull out the LCS/LIS into unmoved_set
  var idx = (max_seq_len === 0 ? -1 : seq_ends[max_seq_len-1]);
  while (idx >= 0) {
    unmoved_set[idx] = true;
    idx = ptrs[idx];
  }

  //////// Main Diff Algorithm

  var old_idx = 0;
  var new_idx = 0;
  var bump_list = [];
  var bump_list_old_idx = [];
  var taken_list = [];

  var scan_to = function(old_j) {
    // old_j <= old_results.length (may scan to end)
    while (old_idx < old_j) {
      var old_doc = old_results[old_idx];
      var is_in_new = new_presence_of_id[old_doc._id];
      if (! is_in_new) {
        observer.removed && observer.removed(old_doc, new_idx + bump_list.length);
      } else {
        if (taken_list.length >= 1 && taken_list[0] === old_idx) {
          // already moved
          taken_list.shift();
        } else {
          // bump!
          bump_list.push(new_idx);
          bump_list_old_idx.push(old_idx);
        }
      }
      old_idx++;
    }
  };

  while (new_idx <= new_results.length) {
    if (new_idx < new_results.length) {
      var new_doc = new_results[new_idx];
      var old_doc_idx = old_index_of_id[new_doc._id];
      if (old_doc_idx === undefined) {
        // insert
        observer.added && observer.added(mdc(new_doc), new_idx + bump_list.length);
      } else {
        var old_doc = old_results[old_doc_idx];
        //var is_unmoved = (old_doc_idx > old_idx); // greedy; not minimal
        var is_unmoved = unmoved_set[new_idx];
        if (is_unmoved) {
          if (old_doc_idx < old_idx)
            Meteor._debug("Assertion failed while diffing: nonmonotonic lcs data");
          // no move
          scan_to(old_doc_idx);
          if (! _.isEqual(old_doc, new_doc)) {
            observer.changed && observer.changed(
              mdc(new_doc), new_idx + bump_list.length, old_doc);
          }
          old_idx++;
        } else {
          // move into place
          var to_idx = new_idx + bump_list.length;
          var from_idx;
          if (old_doc_idx >= old_idx) {
            // move backwards
            from_idx = to_idx + old_doc_idx - old_idx;
            // must take number of "taken" items into account; also use
            // results of this binary search to insert new taken_list entry
            var num_taken_before = _.sortedIndex(taken_list, old_doc_idx);
            from_idx -= num_taken_before;
            taken_list.splice(num_taken_before, 0, old_doc_idx);
          } else {
            // move forwards, from bump list
            // (binary search applies)
            var b = _.indexOf(bump_list_old_idx, old_doc_idx, true);
            if (b < 0)
              Meteor._debug("Assertion failed while diffing: no bumped item");
            from_idx = bump_list[b] + b;
            to_idx--;
            bump_list.splice(b, 1);
            bump_list_old_idx.splice(b, 1);
          }
          if (from_idx != to_idx)
            observer.moved && observer.moved(mdc(old_doc), from_idx, to_idx);
          if (! _.isEqual(old_doc, new_doc)) {
            observer.changed && observer.changed(mdc(new_doc), to_idx, old_doc);
          }
        }
      }
    } else {
      scan_to(old_results.length);
    }
    new_idx++;
  }
  if (bump_list.length > 0) {
    Meteor._debug(old_results);
    Meteor._debug(new_results);
    Meteor._debug("Assertion failed while diffing: leftover bump_list "+
                  bump_list);
  }

};
<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

define([
    "firebug/lib/trace",
    "firebug/lib/object",
    "firebug/lib/domplate",
    "firebug/lib/locale",
    "firebug/lib/dom",
    "firebug/lib/css",
    "firebug/lib/events",
    "firebug/css/selectorModule",
    "firebug/css/selectorEditor"
],
function(FBTrace, Obj, Domplate, Locale, Dom, Css, Events, CSSSelectorsModule, SelectorEditor) {

with (Domplate) {

// ********************************************************************************************* //
// CSS Computed panel (HTML side panel)

function CSSSelectorsPanel() {}

CSSSelectorsPanel.prototype = Obj.extend(Firebug.Panel,
{
    template: domplate(
    {
        selectorsTag:
            DIV({"class": "selectorTrials a11yCSSView", role: "list", "aria-label":
                Locale.$STR("aria.labels.Elements")},
                TAG("$selectorEditorRow"),
                DIV({"class": "elementsGroups"},
                    FOR("group", "$groups",
                        TAG("$elementsGroupTag", {group: "$group", windows: "$windows"})
                    )
                )),

        selectorEditorRow:
            DIV({"class": "selectorEditorContainer editorContainer a11yFocusNoTab",
                role: "button", "tabindex" : "0",
                "aria-label": Locale.$STR("a11y.labels.press enter to add new selector"),
                onclick: "$onClickEditor"},
                Locale.$STR("css.selector.TryASelector")
            ),

        elementsGroupTag:
            DIV({"class": "elementsGroup foldableGroup", $opened: "$group.opened",
                role: "list", _repObject: "$group"},
                H1({"class": "cssElementsHeader groupHeader focusRow", role: "listitem"},
                    DIV({"class": "twisty", role: "presentation"}),
                    SPAN({"class": "cssElementsLabel groupLabel"}, "$group.selector")
                ),
                TAG("$elementsTag", {elements: "$windows,$group.selector|getElements"})
            ),

        elementsTag:
            TABLE({"class": "cssElementsTable groupContent", role: "list"},
                TBODY({role: "presentation"},
                    FOR("element", "$elements",
                        TR({"class": "focusRow cssElementsRow cssElements",
                                role: "listitem", _repObject: "$element"},
                            TD({"class": "cssElement", role: "presentation"},
                                TAG("$element|getElementTag", {object: "$element"})
                            )
                        )
                    )
                )
            ),

        getElementTag: function(value)
        {
            var rep = Firebug.getRep(value);
            var tag = rep.shortTag ? rep.shortTag : rep.tag;
            return tag;
        },

        getElements: function(windows, selector)
        {
            return CSSSelectorsModule.matchElements(windows, selector);
        },

        onClickEditor: function(event)
        {
            var target = event.currentTarget;
            var panel = Firebug.getElementPanel(target);
            Firebug.Editor.startEditing(target, "");
        }
    }),

    getEditor: function(target, value)
    {
        if (!this.editor)
            this.editor = new CSSSelectorsPanelEditor(this.document);

        return this.editor;
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Extends Panel

    name: "selectors",
    parentPanel: "stylesheet",
    order: 0,

    initialize: function()
    {
        this.groups = [
            {
                selector: "div",
                opened: true
            },
            {
                selector: "p > span",
                opened: true
            }
        ];

        Firebug.Panel.initialize.apply(this, arguments);
    },

    destroy: function(state)
    {
        Firebug.Panel.destroyNode.apply(this, arguments);
    },

    initializeNode: function(oldPanelNode)
    {
        Firebug.Panel.initializeNode.apply(this, arguments);
    },

    destroyNode: function()
    {
        Firebug.Panel.destroyNode.apply(this, arguments);
    },

    supportsObject: function(object)
    {
        return 0;
    },

    show: function(state)
    {
        this.refresh();
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Groups

    refresh: function()
    {
        var parentNode = this.template.selectorsTag.replace(
                {groups: this.groups, windows: this.context.windows}, this.panelNode);
    }
});

function CSSSelectorsPanelEditor(doc)
{
    this.box = this.tag.replace({}, doc, this);
    this.input = this.box;

    Firebug.InlineEditor.prototype.initialize.call(this);
    this.tabNavigation = false;
    this.fixedWidth = true;
}

CSSSelectorsPanelEditor.prototype = domplate(SelectorEditor.prototype,
{
    tag:
        INPUT({"class": "fixedWidthEditor a11yFocusNoTab",
            type: "text",
            title: Locale.$STR("Selector"),
            oninput: "$onInput",
            onkeypress: "$onKeyPress"}
        )
});

//********************************************************************************************* //
//Registration

Firebug.registerPanel(CSSSelectorsPanel);

return CSSSelectorsPanel;

//********************************************************************************************* //
}});
<commit_msg>Allow to add groups<commit_after>/* See license.txt for terms of usage */

define([
    "firebug/lib/trace",
    "firebug/lib/object",
    "firebug/lib/domplate",
    "firebug/lib/locale",
    "firebug/lib/dom",
    "firebug/lib/css",
    "firebug/lib/events",
    "firebug/css/selectorModule",
    "firebug/css/selectorEditor"
],
function(FBTrace, Obj, Domplate, Locale, Dom, Css, Events, CSSSelectorsModule, SelectorEditor) {

with (Domplate) {

// ********************************************************************************************* //
// CSS Computed panel (HTML side panel)

function CSSSelectorsPanel() {}

CSSSelectorsPanel.prototype = Obj.extend(Firebug.Panel,
{
    template: domplate(
    {
        selectorsTag:
            DIV({"class": "selectorTrials a11yCSSView", role: "list", "aria-label":
                Locale.$STR("aria.labels.Elements")},
                TAG("$selectorEditorRow"),
                DIV({"class": "elementsGroups"},
                    FOR("group", "$groups",
                        TAG("$elementsGroupTag", {group: "$group", windows: "$windows"})
                    )
                )),

        selectorEditorRow:
            DIV({"class": "selectorEditorContainer editorContainer a11yFocusNoTab",
                role: "button", "tabindex" : "0",
                "aria-label": Locale.$STR("a11y.labels.press enter to add new selector"),
                onclick: "$onClickEditor"},
                Locale.$STR("css.selector.TryASelector")
            ),

        elementsGroupTag:
            DIV({"class": "elementsGroup foldableGroup", $opened: "$group.opened",
                role: "list", _repObject: "$group"},
                H1({"class": "cssElementsHeader groupHeader focusRow", role: "listitem"},
                    DIV({"class": "twisty", role: "presentation"}),
                    SPAN({"class": "cssElementsLabel groupLabel"}, "$group.selector")
                ),
                TAG("$elementsTag", {elements: "$windows,$group.selector|getElements"})
            ),

        elementsTag:
            TABLE({"class": "cssElementsTable groupContent", role: "list"},
                TBODY({role: "presentation"},
                    FOR("element", "$elements",
                        TR({"class": "focusRow cssElementsRow cssElements",
                                role: "listitem", _repObject: "$element"},
                            TD({"class": "cssElement", role: "presentation"},
                                TAG("$element|getElementTag", {object: "$element"})
                            )
                        )
                    )
                )
            ),

        getElementTag: function(value)
        {
            var rep = Firebug.getRep(value);
            var tag = rep.shortTag ? rep.shortTag : rep.tag;
            return tag;
        },

        getElements: function(windows, selector)
        {
            return CSSSelectorsModule.matchElements(windows, selector);
        },

        onClickEditor: function(event)
        {
            var target = event.currentTarget;
            var panel = Firebug.getElementPanel(target);
            Firebug.Editor.startEditing(target, "");
        }
    }),

    getEditor: function(target, value)
    {
        if (!this.editor)
            this.editor = new CSSSelectorsPanelEditor(this.document);

        return this.editor;
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Extends Panel

    name: "selectors",
    parentPanel: "stylesheet",
    order: 0,

    initialize: function()
    {
        this.groups = [];

        Firebug.Panel.initialize.apply(this, arguments);
    },

    destroy: function(state)
    {
        Firebug.Panel.destroyNode.apply(this, arguments);
    },

    initializeNode: function(oldPanelNode)
    {
        Firebug.Panel.initializeNode.apply(this, arguments);
    },

    destroyNode: function()
    {
        Firebug.Panel.destroyNode.apply(this, arguments);
    },

    supportsObject: function(object)
    {
        return 0;
    },

    show: function(state)
    {
        this.refresh();
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Groups

    addGroup: function(selector)
    {
        var group = {
            selector: selector,
            opened: true
        }
        this.groups.push(group);

        // Append element group to display
        var elementsGroups = this.panelNode.getElementsByClassName("elementsGroups")[0];
        var elementsGroup = this.template.elementsGroupTag.replace(
            {group: group, windows: this.context.windows}, elementsGroups);
    },

    refresh: function()
    {
        var parentNode = this.template.selectorsTag.replace(
                {groups: this.groups, windows: this.context.windows}, this.panelNode);
    }
});

function CSSSelectorsPanelEditor(doc)
{
    this.box = this.tag.replace({}, doc, this);
    this.input = this.box;

    Firebug.InlineEditor.prototype.initialize.call(this);
    this.tabNavigation = false;
    this.fixedWidth = true;
}

CSSSelectorsPanelEditor.prototype = domplate(SelectorEditor.prototype,
{
    tag:
        INPUT({"class": "fixedWidthEditor a11yFocusNoTab",
            type: "text",
            title: Locale.$STR("Selector"),
            oninput: "$onInput",
            onkeypress: "$onKeyPress"}
        ),

    endEditing: function(target, value, cancel)
    {
        if (cancel || value == "")
            return;

        if (this.isValidSelector(value))
            this.panel.addGroup(value);
    },

    isValidSelector: function(value)
    {
        try {
            this.panel.panelNode.querySelector(value);
            return true;
        }
        catch (e)
        {
            return false;
        }
    }
});

//********************************************************************************************* //
//Registration

Firebug.registerPanel(CSSSelectorsPanel);

return CSSSelectorsPanel;

//********************************************************************************************* //
}});
<|endoftext|>


<commit_before>"use strict";

var remote = require('electron').remote;
var Menu = remote.Menu;
var Tray = remote.Tray;
var app = remote.app;
var BrowserWindow = remote.BrowserWindow;
var ipcRenderer = require('electron').ipcRenderer;

var pkg = require('./package.json');
var config = require('./configuration');
var log = require('./logger');
var Eventer = require('./eventer');

var Blink1Service = require('./server/blink1Service');

var mainWindow = BrowserWindow.getAllWindows()[0];

var tray = null;

var MenuMaker = {

    /**
     * Return an array of MenuItem templates
     * @method createBigButtonMenu
     * @return {Array}         MenuItem templates
     */
    createBigButtonMenu: function(withAccelerators) {
        var bigButtonsConfig = config.readSettings('bigButtons') || [];
        var statusButtons = bigButtonsConfig.map( function(bb,idx) {
            return {
                label: "Set: " + bb.name,
                accelerator: (withAccelerators) ? "CommandOrControl+" + (idx+1): null,
                // icon: swatchIcon,
                click: function(/*item*/) {
                    Eventer.emit('playBigButtonUser', idx);
                }
            };

        });
        return statusButtons;
    },

    updateTrayMenu: function() {
        log.msg("MenuMaker.updateTrayMenu");

        var contextMenuTemplate = [
            {  label: 'Blink1Control2 is running', enabled: false},
            {  label: 'status: '+ Blink1Service.getStatusString(), enabled: false },
            {  type:  'separator' }
        ];
        var bigButtonMenu = MenuMaker.createBigButtonMenu();
        // Array.prototype.push.apply( contextMenuTemplate, bigButtonMenu );
        contextMenuTemplate = contextMenuTemplate.concat( bigButtonMenu );

        var contextMenuTemplateB = [
            {  type: "separator" },
            {  label: 'Off / Reset Alerts',
                // accelerator: 'CommandOrControl+R',
                click: function() {
                    Eventer.emit('playBigButtonSys', 'Off');
                }
            },
            // {	label: 'Reset Alerts',
            //     click: function() {
            //         // FIXME: TBD
            //     }
            // },
            {	type: "separator" },
            {	label: 'Open Settings...',
                click: function() {
                    mainWindow.show();
                }
            },
            {	label: 'Quit',
                click: function() {
                    ipcRenderer.send('quitnow');
                }
            }
        ];
        contextMenuTemplate = contextMenuTemplate.concat( contextMenuTemplateB );
        // Array.prototype.push.apply( contextMenuTemplate, contextMenuTemplateB );

            // {	label: 'About ' + pkg.productName,
            // 	click: function() { openAboutWindow(); }
            // },
            // {	type: "separator" },
            // {	label: 'Start minimized',
            //     type: 'checkbox',
            //     checked: config.readSettings('startup:startMinimized'),
            //     click: function(menuItem) {
            //         config.saveSettings('startup:startMinimized', menuItem.checked);
            //     },
            // },
            // {	label: 'Start at login',
            //     type: 'checkbox',
            //     checked: config.readSettings('startup:startAtLogin'),
            //     click: function(menuItem) {
            //         config.saveSettings('startup:startAtLogin', menuItem.checked);
            //         // test on Mac with:  osascript -e 'tell application "System Events" to get the name of every login item'
            //         if( menuItem.checked ) {
            //             blink1ControlAutoLauncher.enable();
            //         } else {
            //             blink1ControlAutoLauncher.disable();
            //         }
            //     }
            // },
            //
            /*
            {	label: 'Off / Reset Alerts',  // Note: only works when DevTools is hiddden, else Cmd-r reloads
                // accelerator: 'CommandOrControl+R',	// accelerator: 'CmdOrCtrl+R',
                click: function() {
                    // mainWindow.webContents.send('resetAlerts', 'woot');
                    PatternsService.stopAllPatterns();
                    Blink1Service.off();
                }
            },
            {	type: "separator" },
            {	label: 'Enable API server',
                type: 'checkbox',
                checked: config.readSettings('apiServer:enabled'),
                click: function(menuItem) {
                    config.saveSettings('apiServer:enabled', menuItem.checked);
                    // mainWindow.webContents.send('reloadConfig', 'apiServer');
                }
            },
            // {	type: "separator" },
            // { 	label: 'Set Status...',
            //     submenu: statusButtons
            // },
            {	type: "separator" },
            {	label: 'Open Settings...',
                click: function() {
                    console.log("Open Settings...");
                    mainWindow.show();
                }
            },
            {	label: 'Quit',
                // accelerator: 'CommandOrControl+Q',
                // selector: 'terminate:',
                click: function() {
                    // app.quit(); // doesn't actually cause app to quit
            		ipcRenderer.send('quitnow');
                }
            }
        ];*/

        var contextMenu = Menu.buildFromTemplate( contextMenuTemplate );
        // tray.popUpContextMenu(contextMenu);
        tray.setContextMenu( contextMenu );

        if (process.platform === 'darwin') {
            app.dock.setMenu( contextMenu ); // Make Dock have same context menu
        }
    },

    setupTrayMenu: function() {
        // var self = this;
        console.log("resourcesPath:",process.resourcesPath, "appPath:",app.getAppPath());
        if( process.platform === 'win32' ) {  // FIXME: make this icon better for Windows
            tray = new Tray( app.getAppPath() + '/images/icons/blink1mk2-icon2-128px.ico' );
        }
        else {
            tray = new Tray( app.getAppPath() + '/images/icons/blink1mk2-icon-16px.png' );
        }
        tray.setToolTip( pkg.productName + ' is running...');

        // delete tray object to eliminate duplicates on reload
        window.onbeforeunload = function(/*e*/) {
            console.log("killing tray");
            if(tray) { tray.destroy(); tray = null; }
        };

        this.updateTrayMenu();

        Eventer.on('deviceUpdated', this.updateTrayMenu);
        Eventer.on('bigButtonsUpdated', this.updateTrayMenu);

        // tray.on('click', function() {
        //     self.showTrayMenu();
        // });
        // tray.on('right-click', function() {
        //     self.showTrayMenu();
        // });
        // tray.on('double-click', function() {
        // });
    },

    setupMainMenu: function() {
        // var swatchIconImg = new Jimp(32, 32, 0xFF0000FF, function (err, image) {
        // };
        // var swatchIconBuffer = new Buffer( 32 * 32 * 4 );
        // for( var i = 0; i < swatchIconBuffer.length; i=i+4) {
        // 	swatchIconBuffer.writeUInt32BE( 0xFF0000FF, i );
        // }
        // var swatchIcon = nativeImage.createFromBuffer( swatchIconBuffer );

        var bigButtonMenu = MenuMaker.createBigButtonMenu(true);

        var controlMenuTemplate = [
            { label: 'Off / Reset Alerts', accelerator: "CommandOrControl+R", click: function() {
                mainWindow.webContents.send('resetAlerts');
            }},
            { type: "separator" }
        ];

        controlMenuTemplate = controlMenuTemplate.concat( bigButtonMenu );

        // Mac-specific menu  (hide, unhide, etc. enables Command-Q )
        var templateMac = [
            {	label: pkg.productName,
                submenu: [
                    // { label: "About Blink1Control", selector: "orderFrontStandardAboutPanel:" },
                    { label: "About Blink1Control2",
                        click: function() {
                            ipcRenderer.send('openAboutWindow');
                        }
                    },
                    // FIXME: make appropriate OS-specific version of Windows
                    { type: 'separator' },
                    { label: 'Hide Blink1Control2', accelerator: "CommandOrControl+H", role: 'hide' },
                    { label: 'Hide Others', accelerator: 'Command+Shift+H', role: 'hideothers' },
                    { label: 'Show All', role: 'unhide' },
                    { type: "separator" },
                    { label: 'Preferences...', accelerator: "CommandOrControl+,",
                        click: function() {
                            mainWindow.webContents.send('openPreferences');
                        }
                    },
                    { type: "separator" },
                    { label: 'Open DevTools', accelerator: 'Alt+Command+I', click: function() {
                        mainWindow.show(); mainWindow.webContents.openDevTools({mode:'bottom'}); }
                    },
                    { type: "separator" },
                    { label: 'Close Window', accelerator: "CommandOrControl+W", role:'close' },
                    { type: "separator" },
                    { label: "Quit", accelerator: "CommandOrControl+Q",
                        click: function() {
                             ipcRenderer.send('quitnow');
                        }
                    }
                ]
            },
            {	label: "Edit",
                submenu: [
                    // { label: "Undo", accelerator: "CmdOrCtrl+Z", role: "undo" },
                    // { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", role: "redo" },
                    // { type: "separator" },
                    { label: "Cut", accelerator: "CmdOrCtrl+X", role: "cut" },
                    { label: "Copy", accelerator: "CmdOrCtrl+C", role: "copy" },
                    { label: "Paste", accelerator: "CmdOrCtrl+V", role: "paste" },
                    { label: "Select All", accelerator: "CmdOrCtrl+A", role: "selectall" }
                ]
            },
            { label: "Control",
                submenu: controlMenuTemplate
            }
        ];
        Menu.setApplicationMenu(Menu.buildFromTemplate(templateMac));

    }

};

module.exports = MenuMaker;
<commit_msg>move Quit menu item to better place on tray menu<commit_after>"use strict";

var remote = require('electron').remote;
var Menu = remote.Menu;
var Tray = remote.Tray;
var app = remote.app;
var BrowserWindow = remote.BrowserWindow;
var ipcRenderer = require('electron').ipcRenderer;

var pkg = require('./package.json');
var config = require('./configuration');
var log = require('./logger');
var Eventer = require('./eventer');

var Blink1Service = require('./server/blink1Service');

var mainWindow = BrowserWindow.getAllWindows()[0];

var tray = null;

var MenuMaker = {

    /**
     * Return an array of MenuItem templates
     * @method createBigButtonMenu
     * @return {Array}         MenuItem templates
     */
    createBigButtonMenu: function(withAccelerators) {
        var bigButtonsConfig = config.readSettings('bigButtons') || [];
        var statusButtons = bigButtonsConfig.map( function(bb,idx) {
            return {
                label: "Set: " + bb.name,
                accelerator: (withAccelerators) ? "CommandOrControl+" + (idx+1): null,
                // icon: swatchIcon,
                click: function(/*item*/) {
                    Eventer.emit('playBigButtonUser', idx);
                }
            };

        });
        return statusButtons;
    },

    updateTrayMenu: function() {
        log.msg("MenuMaker.updateTrayMenu");

        var contextMenuTemplate = [
            {  label: 'Blink1Control2 is running', enabled: false},
            {  label: 'status: '+ Blink1Service.getStatusString(), enabled: false },
            {  type:  'separator' }
        ];
        var bigButtonMenu = MenuMaker.createBigButtonMenu();
        // Array.prototype.push.apply( contextMenuTemplate, bigButtonMenu );
        contextMenuTemplate = contextMenuTemplate.concat( bigButtonMenu );

        var contextMenuTemplateB = [
            {  type: "separator" },
            {  label: 'Off / Reset Alerts',
                // accelerator: 'CommandOrControl+R',
                click: function() {
                    Eventer.emit('playBigButtonSys', 'Off');
                }
            },
            // {	label: 'Reset Alerts',
            //     click: function() {
            //         // FIXME: TBD
            //     }
            // },
            {	type: "separator" },
            {	label: 'Open Settings...',
                click: function() {
                    mainWindow.show();
                }
            }
        ];
        var contextMenuTemplateC = [
            {	label: 'Quit',
                click: function() {
                    ipcRenderer.send('quitnow');
                }
            }
        ];
        contextMenuTemplate = contextMenuTemplate.concat( contextMenuTemplateB );
        // Array.prototype.push.apply( contextMenuTemplate, contextMenuTemplateB );

            // {	label: 'About ' + pkg.productName,
            // 	click: function() { openAboutWindow(); }
            // },
            // {	type: "separator" },
            // {	label: 'Start minimized',
            //     type: 'checkbox',
            //     checked: config.readSettings('startup:startMinimized'),
            //     click: function(menuItem) {
            //         config.saveSettings('startup:startMinimized', menuItem.checked);
            //     },
            // },
            // {	label: 'Start at login',
            //     type: 'checkbox',
            //     checked: config.readSettings('startup:startAtLogin'),
            //     click: function(menuItem) {
            //         config.saveSettings('startup:startAtLogin', menuItem.checked);
            //         // test on Mac with:  osascript -e 'tell application "System Events" to get the name of every login item'
            //         if( menuItem.checked ) {
            //             blink1ControlAutoLauncher.enable();
            //         } else {
            //             blink1ControlAutoLauncher.disable();
            //         }
            //     }
            // },
            //
            /*
            {	label: 'Off / Reset Alerts',  // Note: only works when DevTools is hiddden, else Cmd-r reloads
                // accelerator: 'CommandOrControl+R',	// accelerator: 'CmdOrCtrl+R',
                click: function() {
                    // mainWindow.webContents.send('resetAlerts', 'woot');
                    PatternsService.stopAllPatterns();
                    Blink1Service.off();
                }
            },
            {	type: "separator" },
            {	label: 'Enable API server',
                type: 'checkbox',
                checked: config.readSettings('apiServer:enabled'),
                click: function(menuItem) {
                    config.saveSettings('apiServer:enabled', menuItem.checked);
                    // mainWindow.webContents.send('reloadConfig', 'apiServer');
                }
            },
            // {	type: "separator" },
            // { 	label: 'Set Status...',
            //     submenu: statusButtons
            // },
            {	type: "separator" },
            {	label: 'Open Settings...',
                click: function() {
                    console.log("Open Settings...");
                    mainWindow.show();
                }
            },
            {	label: 'Quit',
                // accelerator: 'CommandOrControl+Q',
                // selector: 'terminate:',
                click: function() {
                    // app.quit(); // doesn't actually cause app to quit
            		ipcRenderer.send('quitnow');
                }
            }
        ];*/

        var contextMenu = Menu.buildFromTemplate( contextMenuTemplate );
        if (process.platform === 'darwin') {
            app.dock.setMenu( contextMenu ); // Make Dock have same context menu
        }
        // add on the Quit button for the Tray but not for the Dock menu above
        contextMenu = Menu.buildFromTemplate( contextMenuTemplate.concat( contextMenuTemplateC ));
        tray.setContextMenu( contextMenu );
    },

    setupTrayMenu: function() {
        // var self = this;
        console.log("resourcesPath:",process.resourcesPath, "appPath:",app.getAppPath());
        if( process.platform === 'win32' ) {  // FIXME: make this icon better for Windows
            tray = new Tray( app.getAppPath() + '/images/icons/blink1mk2-icon2-128px.ico' );
        }
        else {
            tray = new Tray( app.getAppPath() + '/images/icons/blink1mk2-icon-16px.png' );
        }
        tray.setToolTip( pkg.productName + ' is running...');

        // delete tray object to eliminate duplicates on reload
        window.onbeforeunload = function(/*e*/) {
            console.log("killing tray");
            if(tray) { tray.destroy(); tray = null; }
        };

        this.updateTrayMenu();

        Eventer.on('deviceUpdated', this.updateTrayMenu);
        Eventer.on('bigButtonsUpdated', this.updateTrayMenu);

        // tray.on('click', function() {
        //     self.showTrayMenu();
        // });
        // tray.on('right-click', function() {
        //     self.showTrayMenu();
        // });
        // tray.on('double-click', function() {
        // });
    },

    setupMainMenu: function() {
        // var swatchIconImg = new Jimp(32, 32, 0xFF0000FF, function (err, image) {
        // };
        // var swatchIconBuffer = new Buffer( 32 * 32 * 4 );
        // for( var i = 0; i < swatchIconBuffer.length; i=i+4) {
        // 	swatchIconBuffer.writeUInt32BE( 0xFF0000FF, i );
        // }
        // var swatchIcon = nativeImage.createFromBuffer( swatchIconBuffer );

        var bigButtonMenu = MenuMaker.createBigButtonMenu(true);

        var controlMenuTemplate = [
            { label: 'Off / Reset Alerts', accelerator: "CommandOrControl+R", click: function() {
                mainWindow.webContents.send('resetAlerts');
            }},
            { type: "separator" }
        ];

        controlMenuTemplate = controlMenuTemplate.concat( bigButtonMenu );

        // Mac-specific menu  (hide, unhide, etc. enables Command-Q )
        var templateMac = [
            {	label: pkg.productName,
                submenu: [
                    // { label: "About Blink1Control", selector: "orderFrontStandardAboutPanel:" },
                    { label: "About Blink1Control2",
                        click: function() {
                            ipcRenderer.send('openAboutWindow');
                        }
                    },
                    // FIXME: make appropriate OS-specific version of Windows
                    { type: 'separator' },
                    { label: 'Hide Blink1Control2', accelerator: "CommandOrControl+H", role: 'hide' },
                    { label: 'Hide Others', accelerator: 'Command+Shift+H', role: 'hideothers' },
                    { label: 'Show All', role: 'unhide' },
                    { type: "separator" },
                    { label: 'Preferences...', accelerator: "CommandOrControl+,",
                        click: function() {
                            mainWindow.webContents.send('openPreferences');
                        }
                    },
                    { type: "separator" },
                    { label: 'Open DevTools', accelerator: 'Alt+Command+I', click: function() {
                        mainWindow.show(); mainWindow.webContents.openDevTools({mode:'bottom'}); }
                    },
                    { type: "separator" },
                    { label: 'Close Window', accelerator: "CommandOrControl+W", role:'close' },
                    { type: "separator" },
                    { label: "Quit", accelerator: "CommandOrControl+Q",
                        click: function() {
                             ipcRenderer.send('quitnow');
                        }
                    }
                ]
            },
            {	label: "Edit",
                submenu: [
                    // { label: "Undo", accelerator: "CmdOrCtrl+Z", role: "undo" },
                    // { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", role: "redo" },
                    // { type: "separator" },
                    { label: "Cut", accelerator: "CmdOrCtrl+X", role: "cut" },
                    { label: "Copy", accelerator: "CmdOrCtrl+C", role: "copy" },
                    { label: "Paste", accelerator: "CmdOrCtrl+V", role: "paste" },
                    { label: "Select All", accelerator: "CmdOrCtrl+A", role: "selectall" }
                ]
            },
            { label: "Control",
                submenu: controlMenuTemplate
            }
        ];
        Menu.setApplicationMenu(Menu.buildFromTemplate(templateMac));

    }

};

module.exports = MenuMaker;
<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM from 'react-dom'
import Resumable from 'resumablejs'
require('when/monitor/console');
import when from 'when'
import whenSequence from 'when/sequence'
import whenPipeline from 'when/pipeline'
import Config from '../utils/config'
import Logger from '../utils/logger'
import API from '../api'
import {uid} from '../utils/random'

export default class FileField extends React.Component {

  static propTypes = {
    code: React.PropTypes.string,
    disabled: React.PropTypes.bool,
    placeholder: React.PropTypes.string,
    defaultValue: React.PropTypes.string,
    query: React.PropTypes.object,
    mode: React.PropTypes.oneOf('single', 'multi'),
    target: React.PropTypes.string,
    eager: React.PropTypes.bool
  }

  static defaultProps = {
    code: null,
    disabled: false,
    placeholder: '',
    defaultValue: '',
    query: {},
    mode: 'single',
    target: Config.get('app.paths.asset_chunks', '/admin/assets/chunks'),
    assetPath: Config.get('app.paths.assets', '/admin/assets'),
    eager: true
  }

  constructor(props) {
    super(props)
    this.state = {
      focused: false,
      validated: false,
      isValid: null,
      filesFailed: [],
      multi: {
        files: [],
        status: 'WAITING',
        filesComplete: []
      },
      preview: props.defaultValue,
      uploadInProgress: false,
      uploadComplete: false,
      uploadProcessing: false,
      uploadFailed: false,
      fileExists: !!props.defaultValue
    }

    this.constructResumable(props)

    this.api = new API()

    this.isResumableSupported = this.r.support;

    // Return an asset ID if the field is already filled
    this.rPromise = when(this.props.defaultValue)

  }

  constructResumable(props) {
    this.r = new Resumable({
      target: Config.get('api.pathPrefix') + props.target,
      query: props.query,
      withCredentials: true,
      maxFiles: (props.mode === 'single' ? 1 : props.maxFiles)
    });

    this.r.on('fileAdded', this.onFileAdded.bind(this))
    this.r.on('fileProgress', this.onFileProgress.bind(this))
    this.r.on('fileError', this.onFileError.bind(this))
    this.r.on('fileSuccess', this.onFileSuccess.bind(this))
    this.r.on('fileRetry', this.onFileRetry.bind(this))

    this.r.on('complete', this.onComplete.bind(this))
    this.r.on('pause', this.onPause.bind(this))
    this.r.on('cancel', this.onCancel.bind(this))
    this.r.on('error', this.onError.bind(this))
  }

  mountResumable() {
    this.r.assignBrowse(this.refs.browseButton)

    if (this.props.mode === 'multi') {
      this.r.assignDrop(this.refs.dropzone)
    }
  }

  render() {
    const allFilesFailed = this.r.files.length > 0 && this.state.filesFailed.length === this.r.files.length

    if (this.props.mode === 'single') {
      if(allFilesFailed || this.state.uploadFailed) {
        // Show failure state
        return (

Uploading failed.

              Retry

              Choose Files...

        )
      }
      if(this.state.uploadInProgress || this.state.uploadProcessing) {
        // Show progress state
        return (

            <FileProgress progress={this.getOverallProgress()}/>

        )
      }
      if(this.state.uploadComplete) {
        // Show finished state
        return (

                <FilePreview id={this.state.preview} assetPath={this.props.assetPath} />

Change

 Remove

        )
      }
      if(this.state.fileExists) {
        // Show preview when a defaultValue is set
        return (

                <FilePreview id={this.state.preview} assetPath={this.props.assetPath} />

 Change

 Remove

        )
      }
      else {
        // Show an empty chooser
        return (

              Choose File...

        )
      }
    }
    else {
      return (

                  Drop Files Here

OR

              Add Files

              {this.r.files.map(file => {
                let progressClass = 'ui progress'
                if(_.includes(this.state.filesFailed, file.uniqueIdentifier)) {
                  progressClass += ' error'
                }
                return (

{file.fileName}

                )
              })}

      )
    }
  }

  componentDidMount() {
    this.mountResumable()
  }

  componentDidUpdate() {

  }

  clearFiles() {
    while(this.r.files.length > 1) {
      r.files[0].cancel()
    }
    this.rPromise = null
    delete this.r
    this.constructResumable(this.props)
    _.defer(() => this.mountResumable())
    this.setState({filesFailed: [], uploadComplete: false, uploadInProgress: false, uploadFailed: false, fileExists: false, preview: null})
    this.forceUpdate()
  }

  clearAndChoose() {
    this.clearFiles()
  }

  retry() {
    _.forEach(this.r.files, f => f.retry())
    this.setState({filesFailed: [], uploadInProgress: true, uploadComplete: false, uploadFailed: false})
  }

  beginUpload() {
    // Start the upload only if some files have been added
    if(_.isEmpty(this.r.files)) {
      this.rPromise = when(null)
      return
    }
    const single = this.props.mode === 'single'
    this.setState({filesFailed: [], uploadInProgress: true, uploadComplete: false, uploadFailed: false})
    const uploadPromise = () => {
      return when.promise((resolve, reject) => {
          let fileResults = []
          this.r.on('complete', () => resolve(fileResults))
          this.r.on('error', reject)
          this.r.on('fileSuccess', (_file, r) => {
            Logger.log(_file, r)
            let resp = JSON.parse(r)
            let assetId = _.get(resp, 'asset_id', null) || _.get(resp, 'id', null)
            fileResults.push(assetId)
          })
          this.r.upload()
        })
        .tap(() => this.setState({uploadInProgress: false, uploadProcessing: true}))
        .tap(Logger.log.bind(Logger))
    }

  const processPromise = ids => {
    Logger.log("Processing...", ids)
    return whenSequence(
      _.map(
        ids,
        i => {
          return () => this.api.patch(`${this.props.assetPath}/${i}/process`)
        }
      ))
      .then(() => ids)
    }

  this.rPromise = whenPipeline([uploadPromise, processPromise])
    .tap(ids => Logger.log("Uploading and Processing complete", ids))
    .tap(ids => this.setState({preview: _.first(ids)}))
    .then(assetIds => {
      if(single) {
        return assetIds[0]
      }
      else {
        return assetIds
      }
    })
    .tap(() => this.setState({uploadProcessing: false, uploadComplete: true}))
    .tap(Logger.log.bind(Logger))
    .catch(failure => {
      this.setState({uploadProcessing: false, uploadComplete: false, uploadInProgress: false, uploadFailed: true})
      Logger.error(failure)
    })
  }

  getOverallProgress() {
    return this.r.progress()
  }

  onFileAdded(_file) {
    this.forceUpdate()
    if(this.props.mode === 'single' && this.props.eager) {
      this.beginUpload()
    }
  }

  onFileSuccess(file, serverResponse) {
    Logger.log(file, serverResponse)
  }

  onFileError(file, serverResponse) {
    Logger.log(file, serverResponse)
    const newFailures = this.state.filesFailed.concat([file.uniqueIdentifier])
    this.setState({
      filesFailed: newFailures
    })

    if(newFailures.length === this.r.files.length) {
      this.r.assignBrowse(this.refs.reBrowseButton)
    }
  }

  onFileProgress(file) {
    Logger.log(file.fileName, file.progress())
    $(this.refs.fileTable).find(`#${file.uniqueIdentifier}`).progress({percent: Math.floor(file.progress() * 100)})
    if(this.state.uploadInProgress) {
      $(this.refs.wrapper).find(".file.progress").progress({
        percent: Math.ceil(this.getOverallProgress() * 100)
      })
    }
    this.forceUpdate()
  }

  onFileRetry(_file) {
    //this.forceUpdate()
  }

  onComplete() {
    this.setState({
      multi: {
        status: 'COMPLETE'
      }
    })
  }

  onPause() {
    this.setState({
      multi: {
        status: 'PAUSED'
      }
    })
  }

  onCancel() {
    this.setState({
      multi: {
        status: 'CANCELLED'
      }
    })
  }

  onError(message, file) {
    this.setState({
      multi: {
        status: 'FAILED'
      }
    })
  }

  // Ripped straight outta Resumable
  formatSize(size){
    if(size<1024) {
      return size + ' bytes';
    } else if(size<1024*1024) {
      return (size/1024.0).toFixed(0) + ' KB';
    } else if(size<1024*1024*1024) {
      return (size/1024.0/1024.0).toFixed(1) + ' MB';
    } else {
      return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB';
    }
  }

  getValue() {
    // Only begin the upload here if there is no upload in progress and an upload has not already completed
    if(!this.state.fileExists && !this.state.uploadInProgress && !this.state.uploadComplete) {
      this.beginUpload()
    }
    return this.rPromise
  }

  setValue(value) {
    if(value !== null) {
      this.setState({preview: value, fileExists: true})
    }
  }

  getReference() {
    return {[this.props.code]: this}
  }
}

const FileProgress = ({progress}) => {
  if(progress == 1) {
    return (

Processing

 Change

 Remove

    )
  } else  {
    return (

    )
  }
}

class FilePreview extends React.Component {
  render() {
    const {assetPath, id, size = 'medium'} = this.props
    const url = Config.get('api.pathPrefix') + `${assetPath}/${id}/preview`
    if(id) {
      return
    }
    else {
      return null
    }
  }
}
<commit_msg>Fix broken Change button in File Fields<commit_after>import React from 'react'
import ReactDOM from 'react-dom'
import Resumable from 'resumablejs'
require('when/monitor/console');
import when from 'when'
import whenSequence from 'when/sequence'
import whenPipeline from 'when/pipeline'
import Config from '../utils/config'
import Logger from '../utils/logger'
import API from '../api'
import {uid} from '../utils/random'

export default class FileField extends React.Component {

  static propTypes = {
    code: React.PropTypes.string,
    disabled: React.PropTypes.bool,
    placeholder: React.PropTypes.string,
    defaultValue: React.PropTypes.string,
    query: React.PropTypes.object,
    mode: React.PropTypes.oneOf('single', 'multi'),
    target: React.PropTypes.string,
    eager: React.PropTypes.bool
  }

  static defaultProps = {
    code: null,
    disabled: false,
    placeholder: '',
    defaultValue: '',
    query: {},
    mode: 'single',
    target: Config.get('app.paths.asset_chunks', '/admin/assets/chunks'),
    assetPath: Config.get('app.paths.assets', '/admin/assets'),
    eager: true
  }

  constructor(props) {
    super(props)
    this.state = {
      focused: false,
      validated: false,
      isValid: null,
      filesFailed: [],
      multi: {
        files: [],
        status: 'WAITING',
        filesComplete: []
      },
      preview: props.defaultValue,
      uploadInProgress: false,
      uploadComplete: false,
      uploadProcessing: false,
      uploadFailed: false,
      fileExists: !!props.defaultValue
    }

    this.constructResumable(props)

    this.api = new API()

    this.isResumableSupported = this.r.support;

    // Return an asset ID if the field is already filled
    this.rPromise = when(this.props.defaultValue)

  }

  constructResumable(props) {
    this.r = new Resumable({
      target: Config.get('api.pathPrefix') + props.target,
      query: props.query,
      withCredentials: true,
      maxFiles: (props.mode === 'single' ? 1 : props.maxFiles)
    });

    this.r.on('fileAdded', this.onFileAdded.bind(this))
    this.r.on('fileProgress', this.onFileProgress.bind(this))
    this.r.on('fileError', this.onFileError.bind(this))
    this.r.on('fileSuccess', this.onFileSuccess.bind(this))
    this.r.on('fileRetry', this.onFileRetry.bind(this))

    this.r.on('complete', this.onComplete.bind(this))
    this.r.on('pause', this.onPause.bind(this))
    this.r.on('cancel', this.onCancel.bind(this))
    this.r.on('error', this.onError.bind(this))
  }

  mountResumable() {
    this.r.assignBrowse(this.refs.browseButton)

    if (this.props.mode === 'multi') {
      this.r.assignDrop(this.refs.dropzone)
    }
  }

  render() {
    const allFilesFailed = this.r.files.length > 0 && this.state.filesFailed.length === this.r.files.length

    if (this.props.mode === 'single') {
      if(allFilesFailed || this.state.uploadFailed) {
        // Show failure state
        return (

Uploading failed.

              Retry

              Choose Files...

        )
      }
      if(this.state.uploadInProgress || this.state.uploadProcessing) {
        // Show progress state
        return (

            <FileProgress progress={this.getOverallProgress()}/>

        )
      }
      if(this.state.uploadComplete) {
        // Show finished state
        return (

                <FilePreview id={this.state.preview} assetPath={this.props.assetPath} />

Change

 Remove

        )
      }
      if(this.state.fileExists) {
        // Show preview when a defaultValue is set
        return (

                <FilePreview id={this.state.preview} assetPath={this.props.assetPath} />

 Change

 Remove

        )
      }
      else {
        // Show an empty chooser
        return (

              Choose File...

        )
      }
    }
    else {
      return (

                  Drop Files Here

OR

              Add Files

              {this.r.files.map(file => {
                let progressClass = 'ui progress'
                if(_.includes(this.state.filesFailed, file.uniqueIdentifier)) {
                  progressClass += ' error'
                }
                return (

{file.fileName}

                )
              })}

      )
    }
  }

  componentDidMount() {
    this.mountResumable()
  }

  componentDidUpdate() {

  }

  clearFiles() {
    while(this.r.files.length > 1) {
      r.files[0].cancel()
    }
    this.rPromise = when(null)
    delete this.r
    this.constructResumable(this.props)
    _.defer(() => this.mountResumable())
    this.setState({filesFailed: [], uploadComplete: false, uploadInProgress: false, uploadFailed: false, fileExists: false, preview: null})
    this.forceUpdate()
  }

  clearAndChoose() {
    this.clearFiles()
  }

  retry() {
    _.forEach(this.r.files, f => f.retry())
    this.setState({filesFailed: [], uploadInProgress: true, uploadComplete: false, uploadFailed: false})
  }

  beginUpload() {
    // Start the upload only if some files have been added
    if(_.isEmpty(this.r.files)) {
      this.rPromise = when(null)
      return
    }
    const single = this.props.mode === 'single'
    this.setState({filesFailed: [], uploadInProgress: true, uploadComplete: false, uploadFailed: false})
    const uploadPromise = () => {
      return when.promise((resolve, reject) => {
          let fileResults = []
          this.r.on('complete', () => resolve(fileResults))
          this.r.on('error', reject)
          this.r.on('fileSuccess', (_file, r) => {
            Logger.log(_file, r)
            let resp = JSON.parse(r)
            let assetId = _.get(resp, 'asset_id', null) || _.get(resp, 'id', null)
            fileResults.push(assetId)
          })
          this.r.upload()
        })
        .tap(() => this.setState({uploadInProgress: false, uploadProcessing: true}))
        .tap(Logger.log.bind(Logger))
    }

  const processPromise = ids => {
    Logger.log("Processing...", ids)
    return whenSequence(
      _.map(
        ids,
        i => {
          return () => this.api.patch(`${this.props.assetPath}/${i}/process`)
        }
      ))
      .then(() => ids)
    }

  this.rPromise = whenPipeline([uploadPromise, processPromise])
    .tap(ids => Logger.log("Uploading and Processing complete", ids))
    .tap(ids => this.setState({preview: _.first(ids)}))
    .then(assetIds => {
      if(single) {
        return assetIds[0]
      }
      else {
        return assetIds
      }
    })
    .tap(() => this.setState({uploadProcessing: false, uploadComplete: true}))
    .tap(() => this.mountResumable())
    .tap(Logger.log.bind(Logger))
    .catch(failure => {
      this.setState({uploadProcessing: false, uploadComplete: false, uploadInProgress: false, uploadFailed: true})
      Logger.error(failure)
    })
  }

  getOverallProgress() {
    return this.r.progress()
  }

  onFileAdded(_file) {
    this.forceUpdate()
    if(this.props.mode === 'single' && this.props.eager) {
      this.beginUpload()
    }
  }

  onFileSuccess(file, serverResponse) {
    Logger.log(file, serverResponse)
  }

  onFileError(file, serverResponse) {
    Logger.log(file, serverResponse)
    const newFailures = this.state.filesFailed.concat([file.uniqueIdentifier])
    this.setState({
      filesFailed: newFailures
    })

    if(newFailures.length === this.r.files.length) {
      this.r.assignBrowse(this.refs.reBrowseButton)
    }
  }

  onFileProgress(file) {
    Logger.log(file.fileName, file.progress())
    $(this.refs.fileTable).find(`#${file.uniqueIdentifier}`).progress({percent: Math.floor(file.progress() * 100)})
    if(this.state.uploadInProgress) {
      $(this.refs.wrapper).find(".file.progress").progress({
        percent: Math.ceil(this.getOverallProgress() * 100)
      })
    }
    this.forceUpdate()
  }

  onFileRetry(_file) {
    //this.forceUpdate()
  }

  onComplete() {
    this.setState({
      multi: {
        status: 'COMPLETE'
      }
    })
  }

  onPause() {
    this.setState({
      multi: {
        status: 'PAUSED'
      }
    })
  }

  onCancel() {
    this.setState({
      multi: {
        status: 'CANCELLED'
      }
    })
  }

  onError(message, file) {
    this.setState({
      multi: {
        status: 'FAILED'
      }
    })
  }

  // Ripped straight outta Resumable
  formatSize(size){
    if(size<1024) {
      return size + ' bytes';
    } else if(size<1024*1024) {
      return (size/1024.0).toFixed(0) + ' KB';
    } else if(size<1024*1024*1024) {
      return (size/1024.0/1024.0).toFixed(1) + ' MB';
    } else {
      return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB';
    }
  }

  getValue() {
    // Only begin the upload here if there is no upload in progress and an upload has not already completed
    if(!this.state.fileExists && !this.state.uploadInProgress && !this.state.uploadComplete) {
      this.beginUpload()
    }
    return this.rPromise
  }

  setValue(value) {
    if(value !== null) {
      this.setState({preview: value, fileExists: true})
    }
  }

  getReference() {
    return {[this.props.code]: this}
  }
}

const FileProgress = ({progress}) => {
  if(progress == 1) {
    return (

Processing

 Change

 Remove

    )
  } else  {
    return (

    )
  }
}

class FilePreview extends React.Component {
  render() {
    const {assetPath, id, size = 'medium'} = this.props
    const url = Config.get('api.pathPrefix') + `${assetPath}/${id}/preview`
    if(id) {
      return
    }
    else {
      return null
    }
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setTitle } from '../actions';

class About extends Component {

    componentDidMount() {
        this.props.dispatch(setTitle('About'));
    }

    render() {
        const linkStyle = {
            'text-decoration': 'none'
        };

        const listStyle = {
            padding: '0 0 5px 0'
        };

        return (

ExtReact Conference App

                    This app is built with Sencha ExtReact 6.5 and uses the following libraries:

React 15.4

Redux 3.6

React Router 4.1

                    The source code for this app is available here.

        )
    }

};

const mapStateToProps = (state) => {
    return { };
}

export default connect(mapStateToProps)(About);<commit_msg>REAC-237<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setTitle } from '../actions';

class About extends Component {

    componentDidMount() {
        this.props.dispatch(setTitle('About'));
    }

    render() {
        const listStyle = {
            padding: '0 0 5px 0'
        };

        return (

ExtReact Conference App

                    This app is built with Sencha ExtReact 6.5 and uses the following libraries:

React 15.4

Redux 3.6

React Router 4.1

                    The source code for this app is available here.

        )
    }

};

const mapStateToProps = (state) => {
    return { };
}

export default connect(mapStateToProps)(About);<|endoftext|>


<commit_before>import Ember from 'ember';
import flatten from './utils/flatten';

function isRelationshipManaged(relationship){
    return relationship.relationshipMeta.options.managed;
}

export default Ember.Mixin.create({
    /* Public API changes */
    isDirty: Ember.computed('hasDirtyAttributes', '_hasDirtyRelationships', function(){
        return this.get('hasDirtyAttributes') || this.get('_hasDirtyRelationships');
    }),

    rollback(){
        if (this.get('hasDirtyAttributes')){
            this.rollbackAttributes();
        }
        if (this.get('_hasDirtyRelationships')){
            this._rollbackRelationships();
        }
    },

    save(...args){
        return this._super(...args).then( savedModel => {
            savedModel._commitModelAndManagedRelationships();
            return savedModel;
        })
    },

    /* private API */

    _commitModelAndManagedRelationships(commitThisModel = false){
        if (commitThisModel){
        	this._internalModel.adapterWillCommit();
            this._internalModel.updateChangedAttributes();
            this._internalModel.adapterDidCommit();
        }

        this.eachRelationship((name, relationship) => {
            if (relationship.options.managed){
                if (relationship.kind == 'hasMany'){
                    this._commitManagedHasMany(name, relationship.options.noCommitNew);
                } else if (relationship.kind =='belongsTo'){
                    this._commitManagedBelongsTo(name);

                } else {
                    throw new Error(`this relationship type is not supported ${relationship.kind}`);
                }
            }
        });

        // Invalidate all caches for managed and referenced relations
        this._invalidateHasDirtyRelationships();

    },

    _commitManagedBelongsTo(name){
        let currentValue = this.get(name).content;
        if (currentValue && currentValue._commitModelAndManagedRelationships){
            currentValue._commitModelAndManagedRelationships(true);
        }
    },

    _commitManagedHasMany(name, noCommitNew){
        let currentValue = this.get(name).content;
        if (currentValue){
            currentValue.toArray().forEach((model) => {
                if (model && model._commitModelAndManagedRelationships){
                    var commitThisModel = !model.get('isNew') || !noCommitNew
                    model._commitModelAndManagedRelationships(commitThisModel);
                }
            });
        }

    },

    _rollbackRelationships(){
        this.eachRelationship((name, relationship) => {
            if (relationship.options.referenced  || relationship.options.managed){
                if (relationship.kind == 'hasMany'){
                    this._rollbackHasManyRelation(name);
                } else if (relationship.kind =='belongsTo'){
                    this._rollbackBelongsToRelation(name);

                } else {
                    throw new Error(`this relationship type is not supported ${relationship.kind}`);
                }
            }
        });
    },

    _rollbackBelongsToRelation(name){
        let relationship = this.belongsTo(name).belongsToRelationship;
        if (relationship.hasLoaded && this._isBelongsToDirty(relationship)){
            let originalValue = null;
            if (relationship.canonicalState){
                originalValue = this.store.peekRecord(relationship.canonicalState.modelName, relationship.canonicalState.id);
            }

            let currentValue = this.get(name).content;
            this.set(name, originalValue);

            if (originalValue && isRelationshipManaged(relationship) && originalValue.get('isDirty')){
                originalValue.rollback();
            }
        }
    },

    _rollbackHasManyRelation(name){
        let relationship = this.hasMany(name).hasManyRelationship;

        let currentValue = Ember.A(this.get(relationship.key).content);

        currentValue.clear();

        if (relationship.isPolymorphic){
            let originalValueIdsAndTypes = Ember.A(relationship.canonicalState.map(m => {
                return { id: m.id, type:m.modelName }
            }));
            originalValueIdsAndTypes.forEach((e) => {
                currentValue.addObject(this.store.peekRecord(e.type, e.id));
            })
        } else {
            let originalValueIds = Ember.A(relationship.canonicalState.map(m => m.id));

            currentValue.addObjects(this.store.peekAll(relationship.belongsToType).filter(model => {
                return originalValueIds.contains(model.id)
            }));
        }

        if (isRelationshipManaged(relationship)){
            currentValue.forEach( model => {
                if (model.get('isDirty')){
                    model.rollback();
                }
            })
        }
    },

    _defineRelationshipComputedProperty: Ember.on('init', function(){
        const related = Ember.A();

        this.eachRelationship((name, relationship) => {
            if (relationship.options.referenced || relationship.options.managed){
                if (!relationship.options.async){
                    throw new Error('Managed relationships mixin works only with async relationships');
                }

                let relationshipData = {
                    name: name,
                    kind: relationship.kind,
                    managed: Boolean(relationship.options.managed)
                }

                if (relationship.kind == 'hasMany'){
                    relationshipData.keys = [`${name}.[]`];

                    if (relationshipData.managed){
                        relationshipData.keys.push(`${name}.@each.isDirty`);
                    }

                } else if (relationship.kind =='belongsTo'){
                    relationshipData.keys = [name];

                    if (relationshipData.managed){
                        relationshipData.keys.push(`${name}.isDirty`);
                    }

                } else {
                    throw new Error(`this relationship type is not supported ${relationship.kind}`);
                }
                related.addObject(relationshipData);
            }
        });

        if (!Ember.isEmpty(related)){
            Ember.defineProperty(this, '_hasDirtyRelationships', Ember.computed.apply(null, [...flatten(related.mapBy('keys')), '_invalidateCachedValue', function(){
                return this._isAtLeastOneBelongsToDirty(related) || this._isAtLeastOneHasManyDirty(related);
            }]));
        }
    }),

    _isAtLeastOneBelongsToDirty(relationships){
        let belongsTos = relationships.filterBy('kind', 'belongsTo');
        let belongsToRelations = belongsTos.map((r) => this.belongsTo(r.name).belongsToRelationship);

        //We filter out the ones which are not loaded yet, because there cannot be a change from the user side
        let belongsToLoadedRelations = Ember.A(belongsToRelations).filterBy('hasLoaded', true);

        return Ember.A(belongsToLoadedRelations).any((relation) => this._isBelongsToDirty(relation));
    },

    _isAtLeastOneHasManyDirty(relationships){
        let hasManys = relationships.filterBy('kind', 'hasMany');
        let hasManyRelations = hasManys.map((r) => this.hasMany(r.name).hasManyRelationship);

        //We filter out the ones which are not loaded yet, because there cannot be a change from the user side
        let hasManyLoadedRelations = Ember.A(hasManyRelations).filterBy('hasLoaded', true);

        return Ember.A(hasManyLoadedRelations).any((relation) => this._isHasManyDirty(relation));

    },

    _isBelongsToDirty(belongsToRelationship){
        //Working just on async relations
        let currentValue = this.get(belongsToRelationship.key).content;

        function isReferenceChanged(){
            if (belongsToRelationship.canonicalState){
                return !currentValue || belongsToRelationship.canonicalState.id !== currentValue.get('id') ;
            } else {
                return Boolean(currentValue);
            }
        }

        function isTheManegedEntityDirty(){
            if (!currentValue){
                return false;
            }
            return currentValue.get('isDirty');
        }

        return isRelationshipManaged(belongsToRelationship) ? isReferenceChanged() || isTheManegedEntityDirty() : isReferenceChanged();
    },

    _isHasManyDirty(hasManyRelationship){
        //Working just on async relations
        let currentValue = this.get(hasManyRelationship.key).content;
        const ordered = hasManyRelationship.relationshipMeta.options.ordered;

        function isReferenceChanged(){
            if (hasManyRelationship.canonicalState.length !== currentValue.length){
                return true
            }

            if (ordered){
                return Ember.A(hasManyRelationship.canonicalState).any((e, i) => e !== currentValue.currentState[i]);
            } else {
                const currentState = Ember.A(currentValue.currentState);
                return Ember.A(hasManyRelationship.canonicalState).any((e, i) => !currentState.contains(e));
            }

        }

        function isTheManegedEntityDirty(){
            return currentValue.any(model => model.get('isDirty') );
        }
        return isRelationshipManaged(hasManyRelationship) ? isReferenceChanged() || isTheManegedEntityDirty() : isReferenceChanged();

    },

    _invalidateCachedValue: false,

    _invalidateHasDirtyRelationships(){
        this.toggleProperty('_invalidateCachedValue');
    }
});
<commit_msg>models in loading state are not commited<commit_after>import Ember from 'ember';
import flatten from './utils/flatten';

function isRelationshipManaged(relationship){
    return relationship.relationshipMeta.options.managed;
}

export default Ember.Mixin.create({
    /* Public API changes */
    isDirty: Ember.computed('hasDirtyAttributes', '_hasDirtyRelationships', function(){
        return this.get('hasDirtyAttributes') || this.get('_hasDirtyRelationships');
    }),

    rollback(){
        if (this.get('hasDirtyAttributes')){
            this.rollbackAttributes();
        }
        if (this.get('_hasDirtyRelationships')){
            this._rollbackRelationships();
        }
    },

    save(...args){
        return this._super(...args).then( savedModel => {
            savedModel._commitModelAndManagedRelationships();
            return savedModel;
        })
    },

    /* private API */

    _commitModelAndManagedRelationships(commitThisModel = false){
        if (commitThisModel){
        	this._internalModel.adapterWillCommit();
            this._internalModel.updateChangedAttributes();
            this._internalModel.adapterDidCommit();
        }

        this.eachRelationship((name, relationship) => {
            if (relationship.options.managed){
                if (relationship.kind == 'hasMany'){
                    this._commitManagedHasMany(name, relationship.options.noCommitNew);
                } else if (relationship.kind =='belongsTo'){
                    this._commitManagedBelongsTo(name);

                } else {
                    throw new Error(`this relationship type is not supported ${relationship.kind}`);
                }
            }
        });

        // Invalidate all caches for managed and referenced relations
        this._invalidateHasDirtyRelationships();

    },

    _commitManagedBelongsTo(name){
        let currentValue = this.get(name).content;
        if (currentValue && currentValue._commitModelAndManagedRelationships){
            currentValue._commitModelAndManagedRelationships(true);
        }
    },

    _commitManagedHasMany(name, noCommitNew){
        let currentValue = this.get(name).content;
        if (currentValue){
            currentValue.toArray().forEach((model) => {
                if (model && model._commitModelAndManagedRelationships){
                    let commitThisModel = !model.get('isNew') || !model.get('isLoading') || !noCommitNew
                    model._commitModelAndManagedRelationships(commitThisModel);
                }
            });
        }

    },

    _rollbackRelationships(){
        this.eachRelationship((name, relationship) => {
            if (relationship.options.referenced  || relationship.options.managed){
                if (relationship.kind == 'hasMany'){
                    this._rollbackHasManyRelation(name);
                } else if (relationship.kind =='belongsTo'){
                    this._rollbackBelongsToRelation(name);

                } else {
                    throw new Error(`this relationship type is not supported ${relationship.kind}`);
                }
            }
        });
    },

    _rollbackBelongsToRelation(name){
        let relationship = this.belongsTo(name).belongsToRelationship;
        if (relationship.hasLoaded && this._isBelongsToDirty(relationship)){
            let originalValue = null;
            if (relationship.canonicalState){
                originalValue = this.store.peekRecord(relationship.canonicalState.modelName, relationship.canonicalState.id);
            }

            let currentValue = this.get(name).content;
            this.set(name, originalValue);

            if (originalValue && isRelationshipManaged(relationship) && originalValue.get('isDirty')){
                originalValue.rollback();
            }
        }
    },

    _rollbackHasManyRelation(name){
        let relationship = this.hasMany(name).hasManyRelationship;

        let currentValue = Ember.A(this.get(relationship.key).content);

        currentValue.clear();

        if (relationship.isPolymorphic){
            let originalValueIdsAndTypes = Ember.A(relationship.canonicalState.map(m => {
                return { id: m.id, type:m.modelName }
            }));
            originalValueIdsAndTypes.forEach((e) => {
                currentValue.addObject(this.store.peekRecord(e.type, e.id));
            })
        } else {
            let originalValueIds = Ember.A(relationship.canonicalState.map(m => m.id));

            currentValue.addObjects(this.store.peekAll(relationship.belongsToType).filter(model => {
                return originalValueIds.contains(model.id)
            }));
        }

        if (isRelationshipManaged(relationship)){
            currentValue.forEach( model => {
                if (model.get('isDirty')){
                    model.rollback();
                }
            })
        }
    },

    _defineRelationshipComputedProperty: Ember.on('init', function(){
        const related = Ember.A();

        this.eachRelationship((name, relationship) => {
            if (relationship.options.referenced || relationship.options.managed){
                if (!relationship.options.async){
                    throw new Error('Managed relationships mixin works only with async relationships');
                }

                let relationshipData = {
                    name: name,
                    kind: relationship.kind,
                    managed: Boolean(relationship.options.managed)
                }

                if (relationship.kind == 'hasMany'){
                    relationshipData.keys = [`${name}.[]`];

                    if (relationshipData.managed){
                        relationshipData.keys.push(`${name}.@each.isDirty`);
                    }

                } else if (relationship.kind =='belongsTo'){
                    relationshipData.keys = [name];

                    if (relationshipData.managed){
                        relationshipData.keys.push(`${name}.isDirty`);
                    }

                } else {
                    throw new Error(`this relationship type is not supported ${relationship.kind}`);
                }
                related.addObject(relationshipData);
            }
        });

        if (!Ember.isEmpty(related)){
            Ember.defineProperty(this, '_hasDirtyRelationships', Ember.computed.apply(null, [...flatten(related.mapBy('keys')), '_invalidateCachedValue', function(){
                return this._isAtLeastOneBelongsToDirty(related) || this._isAtLeastOneHasManyDirty(related);
            }]));
        }
    }),

    _isAtLeastOneBelongsToDirty(relationships){
        let belongsTos = relationships.filterBy('kind', 'belongsTo');
        let belongsToRelations = belongsTos.map((r) => this.belongsTo(r.name).belongsToRelationship);

        //We filter out the ones which are not loaded yet, because there cannot be a change from the user side
        let belongsToLoadedRelations = Ember.A(belongsToRelations).filterBy('hasLoaded', true);

        return Ember.A(belongsToLoadedRelations).any((relation) => this._isBelongsToDirty(relation));
    },

    _isAtLeastOneHasManyDirty(relationships){
        let hasManys = relationships.filterBy('kind', 'hasMany');
        let hasManyRelations = hasManys.map((r) => this.hasMany(r.name).hasManyRelationship);

        //We filter out the ones which are not loaded yet, because there cannot be a change from the user side
        let hasManyLoadedRelations = Ember.A(hasManyRelations).filterBy('hasLoaded', true);

        return Ember.A(hasManyLoadedRelations).any((relation) => this._isHasManyDirty(relation));

    },

    _isBelongsToDirty(belongsToRelationship){
        //Working just on async relations
        let currentValue = this.get(belongsToRelationship.key).content;

        function isReferenceChanged(){
            if (belongsToRelationship.canonicalState){
                return !currentValue || belongsToRelationship.canonicalState.id !== currentValue.get('id') ;
            } else {
                return Boolean(currentValue);
            }
        }

        function isTheManegedEntityDirty(){
            if (!currentValue){
                return false;
            }
            return currentValue.get('isDirty');
        }

        return isRelationshipManaged(belongsToRelationship) ? isReferenceChanged() || isTheManegedEntityDirty() : isReferenceChanged();
    },

    _isHasManyDirty(hasManyRelationship){
        //Working just on async relations
        let currentValue = this.get(hasManyRelationship.key).content;
        const ordered = hasManyRelationship.relationshipMeta.options.ordered;

        function isReferenceChanged(){
            if (hasManyRelationship.canonicalState.length !== currentValue.length){
                return true
            }

            if (ordered){
                return Ember.A(hasManyRelationship.canonicalState).any((e, i) => e !== currentValue.currentState[i]);
            } else {
                const currentState = Ember.A(currentValue.currentState);
                return Ember.A(hasManyRelationship.canonicalState).any((e, i) => !currentState.contains(e));
            }

        }

        function isTheManegedEntityDirty(){
            return currentValue.any(model => model.get('isDirty') );
        }
        return isRelationshipManaged(hasManyRelationship) ? isReferenceChanged() || isTheManegedEntityDirty() : isReferenceChanged();

    },

    _invalidateCachedValue: false,

    _invalidateHasDirtyRelationships(){
        this.toggleProperty('_invalidateCachedValue');
    }
});
<|endoftext|>